diff --git a/.github/workflows/moodle-plugin-ci.yml b/.github/workflows/moodle-plugin-ci.yml
index 2733a1c..dc47bea 100644
--- a/.github/workflows/moodle-plugin-ci.yml
+++ b/.github/workflows/moodle-plugin-ci.yml
@@ -4,7 +4,7 @@ on: [push, pull_request]
jobs:
test:
- runs-on: ubuntu-latest
+ runs-on: ubuntu-22.04
services:
postgres:
@@ -15,11 +15,14 @@ jobs:
ports:
- 5432:5432
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 3
+
mariadb:
image: mariadb:10.6
env:
MYSQL_USER: 'root'
MYSQL_ALLOW_EMPTY_PASSWORD: "true"
+ MYSQL_CHARACTER_SET_SERVER: "utf8mb4"
+ MYSQL_COLLATION_SERVER: "utf8mb4_unicode_ci"
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval 10s --health-timeout 5s --health-retries 3
@@ -28,72 +31,48 @@ jobs:
fail-fast: false
matrix:
include:
- - php: 8.2
- moodle-branch: MOODLE_403_STABLE
- database: pgsql
- - php: 8.2
- moodle-branch: MOODLE_403_STABLE
- database: mariadb
- - php: 8.1
- moodle-branch: MOODLE_403_STABLE
- database: pgsql
- - php: 8.1
- moodle-branch: MOODLE_403_STABLE
+ - php: 7.4
+ moodle-branch: MOODLE_401_STABLE
database: mariadb
- - php: 8.0
- moodle-branch: MOODLE_403_STABLE
+ - php: 7.4
+ moodle-branch: MOODLE_401_STABLE
database: pgsql
- php: 8.0
- moodle-branch: MOODLE_403_STABLE
+ moodle-branch: MOODLE_401_STABLE
database: mariadb
- - php: 8.2
- moodle-branch: MOODLE_402_STABLE
+ - php: 8.0
+ moodle-branch: MOODLE_401_STABLE
database: pgsql
- - php: 8.2
- moodle-branch: MOODLE_402_STABLE
+ - php: 8.1
+ moodle-branch: MOODLE_401_STABLE
database: mariadb
- php: 8.1
- moodle-branch: MOODLE_402_STABLE
+ moodle-branch: MOODLE_401_STABLE
database: pgsql
- php: 8.1
moodle-branch: MOODLE_402_STABLE
database: mariadb
- - php: 8.0
- moodle-branch: MOODLE_402_STABLE
- database: pgsql
- - php: 8.0
- moodle-branch: MOODLE_402_STABLE
- database: mariadb
- php: 8.1
- moodle-branch: MOODLE_401_STABLE
+ moodle-branch: MOODLE_402_STABLE
database: pgsql
- php: 8.1
- moodle-branch: MOODLE_401_STABLE
- database: mariadb
- - php: 8.0
- moodle-branch: MOODLE_401_STABLE
- database: pgsql
- - php: 8.0
- moodle-branch: MOODLE_401_STABLE
+ moodle-branch: MOODLE_403_STABLE
database: mariadb
- - php: 7.4
- moodle-branch: MOODLE_401_STABLE
+ - php: 8.1
+ moodle-branch: MOODLE_403_STABLE
database: pgsql
- - php: 7.4
- moodle-branch: MOODLE_401_STABLE
+ - php: 8.2
+ moodle-branch: MOODLE_402_STABLE
database: mariadb
- - php: 8.0
- moodle-branch: MOODLE_400_STABLE
+ - php: 8.2
+ moodle-branch: MOODLE_402_STABLE
database: pgsql
- - php: 8.0
- moodle-branch: MOODLE_400_STABLE
+ - php: 8.2
+ moodle-branch: MOODLE_403_STABLE
database: mariadb
- - php: 7.4
- moodle-branch: MOODLE_400_STABLE
+ - php: 8.2
+ moodle-branch: MOODLE_403_STABLE
database: pgsql
- - php: 7.4
- moodle-branch: MOODLE_400_STABLE
- database: mariadb
steps:
- name: Check out repository code
@@ -111,7 +90,7 @@ jobs:
- name: Initialise moodle-plugin-ci
run: |
- composer create-project -n --no-dev --prefer-dist moodlehq/moodle-plugin-ci ci ^3
+ composer create-project -n --no-dev --prefer-dist moodlehq/moodle-plugin-ci ci ^4
echo $(cd ci/bin; pwd) >> $GITHUB_PATH
echo $(cd ci/vendor/bin; pwd) >> $GITHUB_PATH
sudo locale-gen en_AU.UTF-8
@@ -123,9 +102,10 @@ jobs:
env:
DB: ${{ matrix.database }}
MOODLE_BRANCH: ${{ matrix.moodle-branch }}
- IGNORE_PATHS: moodle/tests/fixtures,moodle/Sniffs
- CODECHECKER_IGNORE_PATHS: classes/vendor
- PHPDOCCHECKER_IGNORE_PATHS: classes/vendor
+ IGNORE_PATHS: 'moodle/tests/fixtures,moodle/Sniffs,classes/vendor'
+ PHPCS_IGNORE_PATHS: /^classes\/vendor/
+ PHPDOCCHECKER_IGNORE_PATHS: /^classes\/vendor/
+ MUSTACHE_IGNORE_NAMES: 'report.mustache,questionnaire.mustache'
- name: PHP Lint
if: ${{ always() }}
@@ -143,7 +123,7 @@ jobs:
- name: Moodle Code Checker
if: ${{ always() }}
- run: moodle-plugin-ci codechecker --max-warnings 0 || true
+ run: moodle-plugin-ci phpcs --max-warnings 0 || true
- name: Moodle PHPDoc Checker
if: ${{ always() }}
@@ -158,8 +138,9 @@ jobs:
run: moodle-plugin-ci savepoints
- name: Mustache Lint
+ continue-on-error: true # This step will show errors but will not fail
if: ${{ always() }}
- run: moodle-plugin-ci mustache || true
+ run: moodle-plugin-ci mustache
- name: Grunt
if: ${{ always() }}
@@ -172,5 +153,3 @@ jobs:
- name: Behat features
if: ${{ always() }}
run: moodle-plugin-ci behat --profile chrome
-
-
diff --git a/Gruntfile.js b/Gruntfile.js
deleted file mode 100644
index 9388cd3..0000000
--- a/Gruntfile.js
+++ /dev/null
@@ -1,85 +0,0 @@
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle 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 General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle. If not, see .
-/* jshint node: true, browser: false */
-/* eslint-env node */
-
-/**
- * @copyright 2020 Kevin Tippenhauer
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-/**
- * Grunt configuration
- */
-"use strict";
-
-module.exports = function(grunt) {
-
- // We need to include the core Moodle grunt file too, otherwise we can't run tasks like "amd".
- require("grunt-load-gruntfile")(grunt);
- grunt.loadGruntfile("../../Gruntfile.js");
-
- // Load all grunt tasks.
- grunt.loadNpmTasks("grunt-contrib-watch");
- grunt.loadNpmTasks("grunt-contrib-clean");
- grunt.loadNpmTasks('grunt-contrib-uglify');
-
- grunt.initConfig({
- watch: {
- // If any .less file changes in directory "less" then run the "less" task.
- files: "amd/src/*.js",
- tasks: ["uglify"]
- },
- uglify: {
- development: {
- options: {
- sourceMap: {
- includeSources: true,
- },
- },
- files: [{
- expand: true,
- src: ['*.js'],
- dest: 'amd/build',
- ext: '.min.js',
- cwd: 'amd/src',
- // rename: function (dst, src) {
- // To keep the source js files and make new files as `*.min.js`:
- // return dst + '/' + src.replace('.js', '.min.js');
- // Or to override to src:
- // return src;
- // },
- }]
- }
- },
- // less: {
- // // Production config is also available.
- // development: {
- // options: {
- // // Specifies directories to scan for @import directives when parsing.
- // // Default value is the directory of the source, which is probably what you want.
- // paths: ["less/"],
- // compress: true
- // },
- // files: {
- // "styles.css": "less/styles.less"
- // }
- // },
- // }
- });
-
- // The default task (running "grunt" in console).
- grunt.registerTask("default", ["eslint:amd", "uglify"]);
-};
diff --git a/amd/build/questionnaire.min.js b/amd/build/questionnaire.min.js
index 01e5115..a442c99 100644
--- a/amd/build/questionnaire.min.js
+++ b/amd/build/questionnaire.min.js
@@ -6,6 +6,6 @@
* @copyright 2020 Kevin Tippenhauer
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-define("mod_verbalfeedback/questionnaire",["jquery","core/templates","core/notification","core/ajax","core/str","core/modal_factory","core/modal_events"],(function($,Templates,Notification,Ajax,Str,ModalFactory,ModalEvents){var responses=[],questionnaire=function(){this.registerEvents(),$('[data-region="question-row"]').each((function(){responses[$(this).data("criterionid")]={criterionid:$(this).data("criterionid"),value:null,studentcomment:"",privatecomment:""}}));let questionnaireTable=$('[data-region="questionnaire"]');if(1==questionnaireTable.data("preview"))return;let fromUser=questionnaireTable.data("fromuserid"),toUser=questionnaireTable.data("touserid"),verbalfeedbackId=questionnaireTable.data("verbalfeedbackid"),submissionId=questionnaireTable.data("submissionid");Ajax.call([{methodname:"mod_verbalfeedback_get_responses",args:{verbalfeedbackid:verbalfeedbackId,fromuserid:fromUser,touserid:toUser,submissionid:submissionId}}])[0].done((function(result){$.each(result.responses,(function(){let response=this;responses[response.criterionid].criterionid=response.criterionid,responses[response.criterionid].value=response.value,responses[response.criterionid].studentcomment=response.studentcomment,responses[response.criterionid].privatecomment=response.privatecomment,$('[data-region="question-row"]').each((function(){if($(this).data("criterionid")===response.criterionid){let options=$(this).find(".scaleoption");options&&options.each((function(){let selected=$(this).find("label");(selected.data("value")===response.value||""===selected.data("value")&&null===response.value)&&(selected.removeClass("badge-secondary"),selected.removeClass("badge-info"),selected.addClass("badge-success"))}));let studentcomment=$(this).find(".student-comment.editor_atto_content");studentcomment&&""!==response.studentcomment&&studentcomment.html(response.studentcomment);let privatecomment=$(this).find(".private-comment.editor_atto_content");privatecomment&&""!==response.privatecomment&&privatecomment.html(response.privatecomment)}}))}))})).fail(Notification.exception)};function saveResponses(finalise){$(".student-comment").each((function(){let row=$(this).parents('[data-region="question-row"]'),comment=row.find(".student-comment.editor_atto_content").html();comment='
'==comment?"":comment,responses[row.data("criterionid")].studentcomment=comment})),$(".private-comment").each((function(){let row=$(this).parents('[data-region="question-row"]'),comment=row.find(".private-comment.editor_atto_content").html();comment='
'==comment?"":comment,responses[row.data("criterionid")].privatecomment=comment}));let questionnaireTable=$('[data-region="questionnaire"]'),toUser=questionnaireTable.data("touserid"),toUserFullname=questionnaireTable.data("tousername"),verbalfeedbackId=questionnaireTable.data("verbalfeedbackid"),submissionId=questionnaireTable.data("submissionid");if(questionnaireTable.data("anonymous")&&finalise){let messageStrings=[{key:"finaliseanonymousfeedback",component:"mod_verbalfeedback"},{key:"confirmfinaliseanonymousfeedback",component:"mod_verbalfeedback",param:{name:toUserFullname}}];Str.get_strings(messageStrings,"mod_verbalfeedback").done((function(messages){!function(title,confirmationMessage,verbalfeedbackId,submissionId,toUser,responses,finalise){let confirmButtonTextPromise=Str.get_string("finalise","mod_verbalfeedback"),confirmModalPromise=ModalFactory.create({title:title,body:confirmationMessage,large:!0,type:ModalFactory.types.SAVE_CANCEL});$.when(confirmButtonTextPromise,confirmModalPromise).done((function(confirmButtonText,modal){modal.setSaveButtonText(confirmButtonText),modal.show(),modal.getRoot().on(ModalEvents.hidden,(function(){modal.setBody("")})),modal.getRoot().on(ModalEvents.save,(function(){submitResponses(verbalfeedbackId,submissionId,toUser,responses,finalise)}))}))}(messages[0],messages[1],verbalfeedbackId,submissionId,toUser,responses,finalise)})).fail(Notification.exception)}else submitResponses(verbalfeedbackId,submissionId,toUser,responses,finalise)}function submitResponses(verbalfeedbackId,submissionId,toUser,responses,finalise){let responseObjects=[];for(tuple of Object.entries(responses))null!==tuple[1]&&responseObjects.push(tuple[1]);Ajax.call([{methodname:"mod_verbalfeedback_save_responses",args:{verbalfeedbackid:verbalfeedbackId,submissionid:submissionId,touserid:toUser,responses:responseObjects,complete:finalise}}])[0].done((function(response){Str.get_strings([{key:"responsessaved",component:"mod_verbalfeedback"},{key:"errorresponsesavefailed",component:"mod_verbalfeedback"}]).done((function(messages){let notificationData={};response.result?(notificationData.message=messages[0],notificationData.type="success"):(notificationData.message=messages[1],notificationData.type="error"),Notification.addNotification(notificationData)})).fail(Notification.exception),window.location=response.redirurl})).fail(Notification.exception)}return questionnaire.prototype.registerEvents=function(){$(".scaleoption").click((function(e){e.preventDefault();let row=$(this).parents('[data-region="question-row"]'),options=row.find("label");$.each(options,(function(){if($(this).hasClass("badge-success")){$(this).removeClass("badge-success"),$(this).addClass("badge-secondary");var forId=$(this).attr("for");$("#"+forId).removeAttr("checked")}}));let selected=$(this).find("label");selected.removeClass("badge-secondary"),selected.removeClass("badge-info"),selected.addClass("badge-success"),$("#"+selected.attr("for")).attr("checked","checked");let criterionid=row.data("criterionid");""===selected.data("value")?responses[criterionid].value=null:responses[criterionid].value=selected.data("value")})),$(".scaleoptionlabel").hover((function(e){e.preventDefault(),$(this).hasClass("badge-success")||($(this).hasClass("badge-secondary")?($(this).removeClass("badge-secondary"),$(this).addClass("badge-info")):($(this).addClass("badge-secondary"),$(this).removeClass("badge-info")))})),$(".detail-scaleoption").click((function(e){e.preventDefault();let row=$(this).parents('[data-region="detailed-rating"]'),value=$(this).find(".detail-scaleoptionlabel").data("value");row.find(".student-comment.editor_atto_content").append("")})),$(".detail-scaleoptionlabel").hover((function(e){e.preventDefault(),$(this).hasClass("badge-success")||($(this).hasClass("badge-secondary")?($(this).removeClass("badge-secondary"),$(this).addClass("badge-info")):($(this).addClass("badge-secondary"),$(this).removeClass("badge-info")))})),$("#save-feedback").click((function(){saveResponses(!1)})),$("#submit-feedback").click((function(){saveResponses(!0)})),$(".btn-detail-rating").click((function(e){e.preventDefault();let detailedRating=$(this).parents('[data-region="question-row"]').find(".detailed-rating");detailedRating.hasClass("hidden")?(detailedRating.removeClass("hidden"),$(this).html("−")):(detailedRating.addClass("hidden"),$(this).html("+"))}))},questionnaire}));
+define("mod_verbalfeedback/questionnaire",["jquery","core/templates","core/notification","core/ajax","core/str","core/modal_factory","core/modal_events"],(function($,Templates,Notification,Ajax,Str,ModalFactory,ModalEvents){var responses=[];let editor;const getEditor=function(){return editor||(editor=$(".editor_atto").length>0?"atto":window.tinyMCE?"tiny":"textarea",editor)},setComment=function(row,classSelector,comment){let append=arguments.length>3&&void 0!==arguments[3]&&arguments[3];if("atto"===getEditor()){const editorcontent=row.find(classSelector+".editor_atto_content");return append?void editorcontent.append(""):void editorcontent.html(comment)}const commentId=row.find(classSelector).attr("id");if(commentId){const $input=$("#"+commentId);if("tiny"===getEditor())return append?void window.tinyMCE.get(commentId).insertContent("
"):void window.tinyMCE.get(commentId).setContent(comment);if(append){const oldComment=$input.val();if(""!==oldComment.trim())return void $input.val(oldComment+"\n\n -"+comment)}$input.val(comment)}},getComment=function(row,classSel){if("atto"===getEditor()){let comment=row.find(classSel+".editor_atto_content").html();return""===comment.replace(/<[^>]+>/g,"").trim()?"":comment}const commentId=row.find(classSel).attr("id");if(commentId){let comment="";return comment="tiny"===getEditor()?window.tinyMCE.get(commentId).getContent():$("#"+commentId).val(),""===comment.replace(/<[^>]+>/g,"").trim()?"":comment}return""};var questionnaire=function(){this.registerEvents(),$('[data-region="question-row"]').each((function(){responses[$(this).data("criterionid")]={criterionid:$(this).data("criterionid"),value:null,studentcomment:"",privatecomment:""}}));let questionnaireTable=$('[data-region="questionnaire"]');if(1==questionnaireTable.data("preview"))return;let fromUser=questionnaireTable.data("fromuserid"),toUser=questionnaireTable.data("touserid"),verbalfeedbackId=questionnaireTable.data("verbalfeedbackid"),submissionId=questionnaireTable.data("submissionid");Ajax.call([{methodname:"mod_verbalfeedback_get_responses",args:{verbalfeedbackid:verbalfeedbackId,fromuserid:fromUser,touserid:toUser,submissionid:submissionId}}])[0].done((function(result){$.each(result.responses,(function(){let response=this;responses[response.criterionid].criterionid=response.criterionid,responses[response.criterionid].value=response.value,responses[response.criterionid].studentcomment=response.studentcomment,responses[response.criterionid].privatecomment=response.privatecomment,$('[data-region="question-row"]').each((function(){if($(this).data("criterionid")===response.criterionid){let options=$(this).find(".scaleoption");options&&options.each((function(){let selected=$(this).find("label");(selected.data("value")===response.value||""===selected.data("value")&&null===response.value)&&(selected.removeClass("badge-secondary"),selected.removeClass("badge-info"),selected.addClass("badge-success"))})),""!==response.studentcomment&&setComment($(this),".student-comment",response.studentcomment),""!==response.privatecomment&&setComment($(this),".private-comment",response.privatecomment)}}))}))})).fail(Notification.exception)};function saveResponses(finalise){$(".student-comment").each((function(){let row=$(this).parents('[data-region="question-row"]');responses[row.data("criterionid")].studentcomment=getComment(row,".student-comment")})),$(".private-comment").each((function(){let row=$(this).parents('[data-region="question-row"]');responses[row.data("criterionid")].privatecomment=getComment(row,".private-comment")}));let questionnaireTable=$('[data-region="questionnaire"]'),toUser=questionnaireTable.data("touserid"),toUserFullname=questionnaireTable.data("tousername"),verbalfeedbackId=questionnaireTable.data("verbalfeedbackid"),submissionId=questionnaireTable.data("submissionid");if(questionnaireTable.data("anonymous")&&finalise){let messageStrings=[{key:"finaliseanonymousfeedback",component:"mod_verbalfeedback"},{key:"confirmfinaliseanonymousfeedback",component:"mod_verbalfeedback",param:{name:toUserFullname}}];Str.get_strings(messageStrings,"mod_verbalfeedback").done((function(messages){!function(title,confirmationMessage,verbalfeedbackId,submissionId,toUser,responses,finalise){let confirmButtonTextPromise=Str.get_string("finalise","mod_verbalfeedback"),confirmModalPromise=ModalFactory.create({title:title,body:confirmationMessage,large:!0,type:ModalFactory.types.SAVE_CANCEL});$.when(confirmButtonTextPromise,confirmModalPromise).done((function(confirmButtonText,modal){modal.setSaveButtonText(confirmButtonText),modal.show(),modal.getRoot().on(ModalEvents.hidden,(function(){modal.setBody("")})),modal.getRoot().on(ModalEvents.save,(function(){submitResponses(verbalfeedbackId,submissionId,toUser,responses,finalise)}))}))}(messages[0],messages[1],verbalfeedbackId,submissionId,toUser,responses,finalise)})).fail(Notification.exception)}else submitResponses(verbalfeedbackId,submissionId,toUser,responses,finalise)}function submitResponses(verbalfeedbackId,submissionId,toUser,responses,finalise){let responseObjects=[];for(const tuple of Object.entries(responses))null!==tuple[1]&&responseObjects.push(tuple[1]);Ajax.call([{methodname:"mod_verbalfeedback_save_responses",args:{verbalfeedbackid:verbalfeedbackId,submissionid:submissionId,touserid:toUser,responses:responseObjects,complete:finalise}}])[0].done((function(response){Str.get_strings([{key:"responsessaved",component:"mod_verbalfeedback"},{key:"errorresponsesavefailed",component:"mod_verbalfeedback"}]).done((function(messages){let notificationData={};response.result?(notificationData.message=messages[0],notificationData.type="success"):(notificationData.message=messages[1],notificationData.type="error"),Notification.addNotification(notificationData)})).fail(Notification.exception),window.location=response.redirurl})).fail(Notification.exception)}return questionnaire.prototype.registerEvents=function(){$(".scaleoption").click((function(e){e.preventDefault();let row=$(this).parents('[data-region="question-row"]'),options=row.find("label");$.each(options,(function(){if($(this).hasClass("badge-success")){$(this).removeClass("badge-success"),$(this).addClass("badge-secondary");var forId=$(this).attr("for");$("#"+forId).removeAttr("checked")}}));let selected=$(this).find("label");selected.removeClass("badge-secondary"),selected.removeClass("badge-info"),selected.addClass("badge-success"),$("#"+selected.attr("for")).attr("checked","checked");let criterionid=row.data("criterionid");""===selected.data("value")?responses[criterionid].value=null:responses[criterionid].value=selected.data("value")})),$(".scaleoptionlabel").hover((function(e){e.preventDefault(),$(this).hasClass("badge-success")||($(this).hasClass("badge-secondary")?($(this).removeClass("badge-secondary"),$(this).addClass("badge-info")):($(this).addClass("badge-secondary"),$(this).removeClass("badge-info")))})),$(".detail-scaleoption").click((function(e){e.preventDefault();let row=$(this).parents('[data-region="detailed-rating"]'),value=$(this).find(".detail-scaleoptionlabel").data("value");setComment(row,".student-comment",value,!0)})),$(".detail-scaleoptionlabel").hover((function(e){e.preventDefault(),$(this).hasClass("badge-success")||($(this).hasClass("badge-secondary")?($(this).removeClass("badge-secondary"),$(this).addClass("badge-info")):($(this).addClass("badge-secondary"),$(this).removeClass("badge-info")))})),$("#save-feedback").click((function(){saveResponses(!1)})),$("#submit-feedback").click((function(){saveResponses(!0)})),$(".btn-detail-rating").click((function(e){e.preventDefault();let detailedRating=$(this).parents('[data-region="question-row"]').find(".detailed-rating");detailedRating.hasClass("hidden")?(detailedRating.removeClass("hidden"),$(this).html("−")):(detailedRating.addClass("hidden"),$(this).html("+"))}))},questionnaire}));
//# sourceMappingURL=questionnaire.min.js.map
\ No newline at end of file
diff --git a/amd/build/questionnaire.min.js.map b/amd/build/questionnaire.min.js.map
index 97e0a33..6aaf054 100644
--- a/amd/build/questionnaire.min.js.map
+++ b/amd/build/questionnaire.min.js.map
@@ -1 +1 @@
-{"version":3,"file":"questionnaire.min.js","sources":["../src/questionnaire.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * AMD code for the frequently used comments chooser for the marking guide grading form.\n *\n * @module mod_verbalfeedback/questionnaire\n * @class view\n * @copyright 2020 Kevin Tippenhauer \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery',\n 'core/templates',\n 'core/notification',\n 'core/ajax',\n 'core/str',\n 'core/modal_factory',\n 'core/modal_events'\n], function($, Templates, Notification, Ajax, Str, ModalFactory, ModalEvents) {\n\n var responses = [];\n var questionnaire = function() {\n this.registerEvents();\n\n // Prefill responses array.\n $('[data-region=\"question-row\"]').each(function() {\n responses[$(this).data('criterionid')] = {\n criterionid: $(this).data('criterionid'),\n value: null,\n studentcomment: \"\",\n privatecomment: \"\"\n };\n });\n\n let questionnaireTable = $('[data-region=\"questionnaire\"]');\n\n if(questionnaireTable.data('preview') == true) { // dont use '===' as $preview is '1' not 'true'.\n // do not look for existing submission on preview page\n return;\n }\n\n let fromUser = questionnaireTable.data('fromuserid');\n let toUser = questionnaireTable.data('touserid');\n let verbalfeedbackId = questionnaireTable.data('verbalfeedbackid');\n let submissionId = questionnaireTable.data('submissionid');\n\n let promises = Ajax.call([\n {\n methodname: 'mod_verbalfeedback_get_responses',\n args: {\n verbalfeedbackid: verbalfeedbackId,\n fromuserid: fromUser,\n touserid: toUser,\n submissionid: submissionId\n }\n }\n ]);\n\n promises[0].done(function(result) {\n $.each(result.responses, function() {\n let response = this;\n responses[response.criterionid]['criterionid'] = response.criterionid;\n responses[response.criterionid]['value'] = response.value;\n responses[response.criterionid]['studentcomment'] = response.studentcomment;\n responses[response.criterionid]['privatecomment'] = response.privatecomment;\n\n $('[data-region=\"question-row\"]').each(function() {\n if ($(this).data('criterionid') === response.criterionid) {\n let options = $(this).find('.scaleoption');\n if (options) {\n options.each(function() {\n // Mark selected option as selected.\n let selected = $(this).find('label');\n if (selected.data('value') === response.value) {\n selected.removeClass('badge-secondary');\n selected.removeClass('badge-info');\n selected.addClass('badge-success');\n } else if (selected.data('value') === \"\" && response.value === null) {\n selected.removeClass('badge-secondary');\n selected.removeClass('badge-info');\n selected.addClass('badge-success');\n }\n });\n }\n let studentcomment = $(this).find('.student-comment.editor_atto_content');\n if (studentcomment && response.studentcomment !== '') {\n studentcomment.html(response.studentcomment);\n }\n let privatecomment = $(this).find('.private-comment.editor_atto_content');\n if (privatecomment && response.privatecomment !== '') {\n privatecomment.html(response.privatecomment);\n }\n }\n });\n });\n }).fail(Notification.exception);\n };\n\n questionnaire.prototype.registerEvents = function() {\n $('.scaleoption').click(function(e) {\n e.preventDefault();\n\n let row = $(this).parents('[data-region=\"question-row\"]');\n let options = row.find('label');\n\n // Deselect the option that has been selected.\n $.each(options, function() {\n if ($(this).hasClass('badge-success')) {\n $(this).removeClass('badge-success');\n $(this).addClass('badge-secondary');\n\n var forId = $(this).attr('for');\n var optionRadio = $(\"#\" + forId);\n optionRadio.removeAttr('checked');\n }\n });\n\n // Mark selected option as selected.\n let selected = $(this).find('label');\n selected.removeClass('badge-secondary');\n selected.removeClass('badge-info');\n selected.addClass('badge-success');\n\n // Mark hidden radio button as checked.\n let radio = $(\"#\" + selected.attr('for'));\n radio.attr('checked', 'checked');\n let criterionid = row.data('criterionid');\n\n\n\n // Add this selected value to the array of responses.\n if (selected.data('value') === \"\") { // === is necessary because == \"0\" equals true;\n responses[criterionid]['value'] = null;\n } else {\n responses[criterionid]['value'] = selected.data('value');\n }\n });\n\n $('.scaleoptionlabel').hover(function(e) {\n e.preventDefault();\n\n if (!$(this).hasClass('badge-success')) {\n if ($(this).hasClass('badge-secondary')) {\n $(this).removeClass('badge-secondary');\n $(this).addClass('badge-info');\n } else {\n $(this).addClass('badge-secondary');\n $(this).removeClass('badge-info');\n }\n }\n });\n\n $('.detail-scaleoption').click(function(e) {\n e.preventDefault();\n\n let row = $(this).parents('[data-region=\"detailed-rating\"]');\n let value = $(this).find('.detail-scaleoptionlabel').data(\"value\");\n let studentComment = row.find('.student-comment.editor_atto_content');\n studentComment.append(\"\");\n\n });\n\n $('.detail-scaleoptionlabel').hover(function(e) {\n e.preventDefault();\n\n if (!$(this).hasClass('badge-success')) {\n if ($(this).hasClass('badge-secondary')) {\n $(this).removeClass('badge-secondary');\n $(this).addClass('badge-info');\n } else {\n $(this).addClass('badge-secondary');\n $(this).removeClass('badge-info');\n }\n }\n });\n\n $(\"#save-feedback\").click(function() {\n saveResponses(false);\n });\n\n $(\"#submit-feedback\").click(function() {\n saveResponses(true);\n });\n\n $(\".btn-detail-rating\").click(function(e) {\n e.preventDefault();\n let row = $(this).parents('[data-region=\"question-row\"]');\n let detailedRating = row.find(\".detailed-rating\");\n if(detailedRating.hasClass(\"hidden\")) {\n detailedRating.removeClass(\"hidden\");\n $(this).html(\"−\");\n } else {\n detailedRating.addClass(\"hidden\");\n $(this).html(\"+\");\n }\n\n });\n };\n\n /**\n * Save the responses.\n *\n * @param {boolean} finalise\n */\n function saveResponses(finalise) {\n\n $('.student-comment').each(function() {\n let row = $(this).parents('[data-region=\"question-row\"]');\n let comment = row.find('.student-comment.editor_atto_content').html();\n comment = (comment == \"
\" ? \"\": comment); // drop empty comments\n responses[row.data('criterionid')]['studentcomment'] = comment;\n });\n $('.private-comment').each(function() {\n let row = $(this).parents('[data-region=\"question-row\"]');\n let comment = row.find('.private-comment.editor_atto_content').html();\n comment = (comment == \"
\" ? \"\": comment); // drop empty comments\n responses[row.data('criterionid')]['privatecomment'] = comment;\n });\n\n let questionnaireTable = $('[data-region=\"questionnaire\"]');\n let toUser = questionnaireTable.data('touserid');\n let toUserFullname = questionnaireTable.data('tousername');\n let verbalfeedbackId = questionnaireTable.data('verbalfeedbackid');\n let submissionId = questionnaireTable.data('submissionid');\n let anonymous = questionnaireTable.data('anonymous');\n\n if (anonymous && finalise) {\n // Show confirmation dialogue to anonymise the feedback responses.\n let messageStrings = [\n {\n key: 'finaliseanonymousfeedback',\n component: 'mod_verbalfeedback'\n },\n {\n key: 'confirmfinaliseanonymousfeedback',\n component: 'mod_verbalfeedback',\n param: {\n 'name': toUserFullname\n }\n }\n ];\n\n Str.get_strings(messageStrings, 'mod_verbalfeedback').done(function(messages) {\n showConfirmationDialogue(messages[0], messages[1], verbalfeedbackId, submissionId, toUser, responses, finalise);\n }).fail(Notification.exception);\n } else {\n // Just save the responses.\n submitResponses(verbalfeedbackId, submissionId, toUser, responses, finalise);\n }\n }\n\n /**\n * Send the responses to the server.\n *\n * @param {number} verbalfeedbackId\n * @param {number} submissionId\n * @param {number} toUser\n * @param {array} responses\n * @param {boolean} finalise\n */\n function submitResponses(verbalfeedbackId, submissionId, toUser, responses, finalise) {\n let responseObjects = [];\n for (tuple of Object.entries(responses)) {\n if (tuple[1] !== null) {\n responseObjects.push(tuple[1]);\n }\n }\n\n let promises = Ajax.call([\n {\n methodname: 'mod_verbalfeedback_save_responses',\n args: {\n verbalfeedbackid: verbalfeedbackId,\n submissionid: submissionId,\n touserid: toUser,\n responses: responseObjects,\n complete: finalise\n }\n }\n ]);\n\n promises[0].done(function(response) {\n // console.log(response);\n let messageStrings = [\n {\n key: 'responsessaved',\n component: 'mod_verbalfeedback'\n },\n {\n key: 'errorresponsesavefailed',\n component: 'mod_verbalfeedback'\n }\n ];\n\n Str.get_strings(messageStrings).done(function(messages) {\n let notificationData = {};\n if (response.result) {\n notificationData.message = messages[0];\n notificationData.type = \"success\";\n } else {\n notificationData.message = messages[1];\n notificationData.type = \"error\";\n }\n Notification.addNotification(notificationData);\n }).fail(Notification.exception);\n\n window.location = response.redirurl;\n }).fail(Notification.exception);\n }\n\n /**\n * Renders the confirmation dialogue to submit and finalise the responses.\n *\n * @param {string} title\n * @param {string} confirmationMessage\n * @param {number} verbalfeedbackId\n * @param {number} submissionId\n * @param {number} toUser\n * @param {Array} responses\n * @param {boolean} finalise\n */\n function showConfirmationDialogue(title, confirmationMessage, verbalfeedbackId, submissionId, toUser, responses, finalise) {\n let confirmButtonTextPromise = Str.get_string('finalise', 'mod_verbalfeedback');\n let confirmModalPromise = ModalFactory.create({\n title: title,\n body: confirmationMessage,\n large: true,\n type: ModalFactory.types.SAVE_CANCEL\n });\n $.when(confirmButtonTextPromise, confirmModalPromise).done(function(confirmButtonText, modal) {\n modal.setSaveButtonText(confirmButtonText);\n\n // Display the dialogue.\n modal.show();\n\n // On hide handler.\n modal.getRoot().on(ModalEvents.hidden, function() {\n // Empty modal contents when it's hidden.\n modal.setBody('');\n });\n\n modal.getRoot().on(ModalEvents.save, function() {\n submitResponses(verbalfeedbackId, submissionId, toUser, responses, finalise);\n });\n });\n\n }\n\n return questionnaire;\n});\n"],"names":["define","$","Templates","Notification","Ajax","Str","ModalFactory","ModalEvents","responses","questionnaire","registerEvents","each","this","data","criterionid","value","studentcomment","privatecomment","questionnaireTable","fromUser","toUser","verbalfeedbackId","submissionId","call","methodname","args","verbalfeedbackid","fromuserid","touserid","submissionid","done","result","response","options","find","selected","removeClass","addClass","html","fail","exception","saveResponses","finalise","row","parents","comment","toUserFullname","messageStrings","key","component","param","get_strings","messages","title","confirmationMessage","confirmButtonTextPromise","get_string","confirmModalPromise","create","body","large","type","types","SAVE_CANCEL","when","confirmButtonText","modal","setSaveButtonText","show","getRoot","on","hidden","setBody","save","submitResponses","showConfirmationDialogue","responseObjects","tuple","Object","entries","push","complete","notificationData","message","addNotification","window","location","redirurl","prototype","click","e","preventDefault","hasClass","forId","attr","removeAttr","hover","append","detailedRating"],"mappings":";;;;;;;;AAuBAA,0CAAO,CAAC,SACJ,iBACA,oBACA,YACA,WACA,qBACA,sBACD,SAASC,EAAGC,UAAWC,aAAcC,KAAMC,IAAKC,aAAcC,iBAEzDC,UAAY,GACZC,cAAgB,gBACXC,iBAGLT,EAAE,gCAAgCU,MAAK,WACnCH,UAAUP,EAAEW,MAAMC,KAAK,gBAAkB,CACrCC,YAAab,EAAEW,MAAMC,KAAK,eAC1BE,MAAO,KACPC,eAAgB,GAChBC,eAAgB,WAIpBC,mBAAqBjB,EAAE,oCAEc,GAAtCiB,mBAAmBL,KAAK,sBAKvBM,SAAWD,mBAAmBL,KAAK,cACnCO,OAASF,mBAAmBL,KAAK,YACjCQ,iBAAmBH,mBAAmBL,KAAK,oBAC3CS,aAAeJ,mBAAmBL,KAAK,gBAE5BT,KAAKmB,KAAK,CACrB,CACIC,WAAY,mCACZC,KAAM,CACFC,iBAAkBL,iBAClBM,WAAYR,SACZS,SAAUR,OACVS,aAAcP,iBAKjB,GAAGQ,MAAK,SAASC,QACtB9B,EAAEU,KAAKoB,OAAOvB,WAAW,eACnBwB,SAAWpB,KACbJ,UAAUwB,SAASlB,aAAnB,YAAiDkB,SAASlB,YAC1DN,UAAUwB,SAASlB,aAAnB,MAA2CkB,SAASjB,MACpDP,UAAUwB,SAASlB,aAAnB,eAAoDkB,SAAShB,eAC7DR,UAAUwB,SAASlB,aAAnB,eAAoDkB,SAASf,eAE7DhB,EAAE,gCAAgCU,MAAK,cAC/BV,EAAEW,MAAMC,KAAK,iBAAmBmB,SAASlB,YAAa,KACpDmB,QAAUhC,EAAEW,MAAMsB,KAAK,gBACrBD,SACAA,QAAQtB,MAAK,eAELwB,SAAWlC,EAAEW,MAAMsB,KAAK,UACxBC,SAAStB,KAAK,WAAamB,SAASjB,OAIF,KAA3BoB,SAAStB,KAAK,UAAsC,OAAnBmB,SAASjB,SAHjDoB,SAASC,YAAY,mBACrBD,SAASC,YAAY,cACrBD,SAASE,SAAS,yBAQ1BrB,eAAiBf,EAAEW,MAAMsB,KAAK,wCAC9BlB,gBAA8C,KAA5BgB,SAAShB,gBAC3BA,eAAesB,KAAKN,SAAShB,oBAE7BC,eAAiBhB,EAAEW,MAAMsB,KAAK,wCAC9BjB,gBAA8C,KAA5Be,SAASf,gBAC3BA,eAAeqB,KAAKN,SAASf,0BAK9CsB,KAAKpC,aAAaqC,qBA6GhBC,cAAcC,UAEnBzC,EAAE,oBAAoBU,MAAK,eACnBgC,IAAM1C,EAAEW,MAAMgC,QAAQ,gCACtBC,QAAUF,IAAIT,KAAK,wCAAwCI,OAC/DO,QAAsB,mDAAXA,QAAmE,GAAIA,QAClFrC,UAAUmC,IAAI9B,KAAK,gBAAnB,eAAuDgC,WAE3D5C,EAAE,oBAAoBU,MAAK,eACnBgC,IAAM1C,EAAEW,MAAMgC,QAAQ,gCACtBC,QAAUF,IAAIT,KAAK,wCAAwCI,OAC/DO,QAAsB,mDAAXA,QAAmE,GAAIA,QAClFrC,UAAUmC,IAAI9B,KAAK,gBAAnB,eAAuDgC,eAGvD3B,mBAAqBjB,EAAE,iCACvBmB,OAASF,mBAAmBL,KAAK,YACjCiC,eAAiB5B,mBAAmBL,KAAK,cACzCQ,iBAAmBH,mBAAmBL,KAAK,oBAC3CS,aAAeJ,mBAAmBL,KAAK,mBAC3BK,mBAAmBL,KAAK,cAEvB6B,SAAU,KAEnBK,eAAiB,CACjB,CACIC,IAAK,4BACLC,UAAW,sBAEf,CACID,IAAK,mCACLC,UAAW,qBACXC,MAAO,MACKJ,kBAKpBzC,IAAI8C,YAAYJ,eAAgB,sBAAsBjB,MAAK,SAASsB,oBA+E1CC,MAAOC,oBAAqBjC,iBAAkBC,aAAcF,OAAQZ,UAAWkC,cAC3Ga,yBAA2BlD,IAAImD,WAAW,WAAY,sBACpDC,oBAAsBnD,aAAaoD,OAAO,CAC1CL,MAAOA,MACPM,KAAML,oBACNM,OAAO,EACPC,KAAMvD,aAAawD,MAAMC,cAE7B9D,EAAE+D,KAAKT,yBAA0BE,qBAAqB3B,MAAK,SAASmC,kBAAmBC,OACnFA,MAAMC,kBAAkBF,mBAGxBC,MAAME,OAGNF,MAAMG,UAAUC,GAAG/D,YAAYgE,QAAQ,WAEnCL,MAAMM,QAAQ,OAGlBN,MAAMG,UAAUC,GAAG/D,YAAYkE,MAAM,WACjCC,gBAAgBrD,iBAAkBC,aAAcF,OAAQZ,UAAWkC,gBAnGnEiC,CAAyBvB,SAAS,GAAIA,SAAS,GAAI/B,iBAAkBC,aAAcF,OAAQZ,UAAWkC,aACvGH,KAAKpC,aAAaqC,gBAGrBkC,gBAAgBrD,iBAAkBC,aAAcF,OAAQZ,UAAWkC,mBAalEgC,gBAAgBrD,iBAAkBC,aAAcF,OAAQZ,UAAWkC,cACpEkC,gBAAkB,OACjBC,SAASC,OAAOC,QAAQvE,WACV,OAAbqE,MAAM,IACRD,gBAAgBI,KAAKH,MAAM,IAIhBzE,KAAKmB,KAAK,CACrB,CACIC,WAAY,oCACZC,KAAM,CACFC,iBAAkBL,iBAClBQ,aAAcP,aACdM,SAAUR,OACVZ,UAAWoE,gBACXK,SAAUvC,aAKb,GAAGZ,MAAK,SAASE,UAatB3B,IAAI8C,YAXe,CACf,CACIH,IAAK,iBACLC,UAAW,sBAEf,CACID,IAAK,0BACLC,UAAW,wBAIanB,MAAK,SAASsB,cACxC8B,iBAAmB,GACjBlD,SAASD,QACTmD,iBAAiBC,QAAU/B,SAAS,GACpC8B,iBAAiBrB,KAAO,YAExBqB,iBAAiBC,QAAU/B,SAAS,GACpC8B,iBAAiBrB,KAAO,SAE5B1D,aAAaiF,gBAAgBF,qBAC9B3C,KAAKpC,aAAaqC,WAErB6C,OAAOC,SAAWtD,SAASuD,YAC5BhD,KAAKpC,aAAaqC,kBAjNzB/B,cAAc+E,UAAU9E,eAAiB,WACrCT,EAAE,gBAAgBwF,OAAM,SAASC,GAC7BA,EAAEC,qBAEEhD,IAAM1C,EAAEW,MAAMgC,QAAQ,gCACtBX,QAAUU,IAAIT,KAAK,SAGvBjC,EAAEU,KAAKsB,SAAS,cACRhC,EAAEW,MAAMgF,SAAS,iBAAkB,CACnC3F,EAAEW,MAAMwB,YAAY,iBACpBnC,EAAEW,MAAMyB,SAAS,uBAEbwD,MAAQ5F,EAAEW,MAAMkF,KAAK,OACP7F,EAAE,IAAM4F,OACdE,WAAW,mBAK3B5D,SAAWlC,EAAEW,MAAMsB,KAAK,SAC5BC,SAASC,YAAY,mBACrBD,SAASC,YAAY,cACrBD,SAASE,SAAS,iBAGNpC,EAAE,IAAMkC,SAAS2D,KAAK,QAC5BA,KAAK,UAAW,eAClBhF,YAAc6B,IAAI9B,KAAK,eAKI,KAA3BsB,SAAStB,KAAK,SACdL,UAAUM,aAAV,MAAkC,KAElCN,UAAUM,aAAV,MAAkCqB,SAAStB,KAAK,YAIxDZ,EAAE,qBAAqB+F,OAAM,SAASN,GAClCA,EAAEC,iBAEG1F,EAAEW,MAAMgF,SAAS,mBACd3F,EAAEW,MAAMgF,SAAS,oBACjB3F,EAAEW,MAAMwB,YAAY,mBACpBnC,EAAEW,MAAMyB,SAAS,gBAEjBpC,EAAEW,MAAMyB,SAAS,mBACjBpC,EAAEW,MAAMwB,YAAY,mBAKhCnC,EAAE,uBAAuBwF,OAAM,SAASC,GACpCA,EAAEC,qBAEEhD,IAAM1C,EAAEW,MAAMgC,QAAQ,mCACtB7B,MAAQd,EAAEW,MAAMsB,KAAK,4BAA4BrB,KAAK,SACrC8B,IAAIT,KAAK,wCACf+D,OAAO,WAAalF,MAAQ,iBAI/Cd,EAAE,4BAA4B+F,OAAM,SAASN,GACzCA,EAAEC,iBAEG1F,EAAEW,MAAMgF,SAAS,mBACd3F,EAAEW,MAAMgF,SAAS,oBACjB3F,EAAEW,MAAMwB,YAAY,mBACpBnC,EAAEW,MAAMyB,SAAS,gBAEjBpC,EAAEW,MAAMyB,SAAS,mBACjBpC,EAAEW,MAAMwB,YAAY,mBAKhCnC,EAAE,kBAAkBwF,OAAM,WACtBhD,eAAc,MAGlBxC,EAAE,oBAAoBwF,OAAM,WACxBhD,eAAc,MAGlBxC,EAAE,sBAAsBwF,OAAM,SAASC,GACnCA,EAAEC,qBAEEO,eADMjG,EAAEW,MAAMgC,QAAQ,gCACDV,KAAK,oBAC3BgE,eAAeN,SAAS,WACvBM,eAAe9D,YAAY,UAC3BnC,EAAEW,MAAM0B,KAAK,OAEb4D,eAAe7D,SAAS,UACxBpC,EAAEW,MAAM0B,KAAK,UA2JlB7B"}
\ No newline at end of file
+{"version":3,"file":"questionnaire.min.js","sources":["../src/questionnaire.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * AMD code for the frequently used comments chooser for the marking guide grading form.\n *\n * @module mod_verbalfeedback/questionnaire\n * @class view\n * @copyright 2020 Kevin Tippenhauer \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery',\n 'core/templates',\n 'core/notification',\n 'core/ajax',\n 'core/str',\n 'core/modal_factory',\n 'core/modal_events'\n], function($, Templates, Notification, Ajax, Str, ModalFactory, ModalEvents) {\n\n var responses = [];\n\n let editor;\n const getEditor = function() {\n if (editor) {\n return editor;\n }\n if ($('.editor_atto').length > 0) {\n editor = 'atto';\n } else if (window.tinyMCE) {\n editor = 'tiny';\n } else {\n editor = 'textarea';\n }\n return editor;\n };\n\n const setComment = function(row, classSelector, comment, append = false) {\n if (getEditor() === 'atto') {\n const editorcontent = row.find(classSelector + '.editor_atto_content');\n if (append) {\n editorcontent.append(\"\");\n return;\n }\n editorcontent.html(comment);\n return;\n }\n const commentId = row.find(classSelector).attr('id');\n if (commentId) {\n const $input = $('#' + commentId);\n if (getEditor() === 'tiny') {\n if (append) {\n window.tinyMCE.get(commentId).insertContent('
');\n return;\n }\n window.tinyMCE.get(commentId).setContent(comment);\n return;\n }\n if (append) {\n const oldComment = $input.val();\n if (oldComment.trim() !== '') {\n $input.val(oldComment + \"\\n\\n -\" + comment);\n return;\n }\n }\n $input.val(comment);\n }\n };\n\n const getComment = function (row, classSel) {\n if (getEditor() === 'atto') {\n let comment = row.find(classSel + '.editor_atto_content').html();\n return comment.replace(/<[^>]+>/g,'').trim() === '' ? '' : comment; // drop empty comments\n }\n const commentId = row.find(classSel).attr('id');\n if (commentId) {\n let comment = '';\n if (getEditor() === 'tiny') {\n comment = window.tinyMCE.get(commentId).getContent();\n } else {\n comment = $('#' + commentId).val();\n }\n return comment.replace(/<[^>]+>/g,'').trim() === '' ? '' : comment; // drop empty comments\n }\n return '';\n };\n\n var questionnaire = function() {\n this.registerEvents();\n\n // Prefill responses array.\n $('[data-region=\"question-row\"]').each(function() {\n responses[$(this).data('criterionid')] = {\n criterionid: $(this).data('criterionid'),\n value: null,\n studentcomment: \"\",\n privatecomment: \"\"\n };\n });\n\n let questionnaireTable = $('[data-region=\"questionnaire\"]');\n\n if(questionnaireTable.data('preview') == true) { // dont use '===' as $preview is '1' not 'true'.\n // do not look for existing submission on preview page\n return;\n }\n\n let fromUser = questionnaireTable.data('fromuserid');\n let toUser = questionnaireTable.data('touserid');\n let verbalfeedbackId = questionnaireTable.data('verbalfeedbackid');\n let submissionId = questionnaireTable.data('submissionid');\n\n let promises = Ajax.call([\n {\n methodname: 'mod_verbalfeedback_get_responses',\n args: {\n verbalfeedbackid: verbalfeedbackId,\n fromuserid: fromUser,\n touserid: toUser,\n submissionid: submissionId\n }\n }\n ]);\n\n promises[0].done(function(result) {\n $.each(result.responses, function() {\n let response = this;\n responses[response.criterionid]['criterionid'] = response.criterionid;\n responses[response.criterionid]['value'] = response.value;\n responses[response.criterionid]['studentcomment'] = response.studentcomment;\n responses[response.criterionid]['privatecomment'] = response.privatecomment;\n\n $('[data-region=\"question-row\"]').each(function() {\n if ($(this).data('criterionid') === response.criterionid) {\n let options = $(this).find('.scaleoption');\n if (options) {\n options.each(function() {\n // Mark selected option as selected.\n let selected = $(this).find('label');\n if (selected.data('value') === response.value) {\n selected.removeClass('badge-secondary');\n selected.removeClass('badge-info');\n selected.addClass('badge-success');\n } else if (selected.data('value') === \"\" && response.value === null) {\n selected.removeClass('badge-secondary');\n selected.removeClass('badge-info');\n selected.addClass('badge-success');\n }\n });\n }\n if (response.studentcomment !== '') {\n setComment($(this), '.student-comment', response.studentcomment);\n }\n if (response.privatecomment !== '') {\n setComment($(this), '.private-comment', response.privatecomment);\n }\n }\n });\n });\n }).fail(Notification.exception);\n };\n\n questionnaire.prototype.registerEvents = function() {\n $('.scaleoption').click(function(e) {\n e.preventDefault();\n\n let row = $(this).parents('[data-region=\"question-row\"]');\n let options = row.find('label');\n\n // Deselect the option that has been selected.\n $.each(options, function() {\n if ($(this).hasClass('badge-success')) {\n $(this).removeClass('badge-success');\n $(this).addClass('badge-secondary');\n\n var forId = $(this).attr('for');\n var optionRadio = $(\"#\" + forId);\n optionRadio.removeAttr('checked');\n }\n });\n\n // Mark selected option as selected.\n let selected = $(this).find('label');\n selected.removeClass('badge-secondary');\n selected.removeClass('badge-info');\n selected.addClass('badge-success');\n\n // Mark hidden radio button as checked.\n let radio = $(\"#\" + selected.attr('for'));\n radio.attr('checked', 'checked');\n let criterionid = row.data('criterionid');\n\n // Add this selected value to the array of responses.\n if (selected.data('value') === \"\") { // === is necessary because == \"0\" equals true;\n responses[criterionid]['value'] = null;\n } else {\n responses[criterionid]['value'] = selected.data('value');\n }\n });\n\n $('.scaleoptionlabel').hover(function(e) {\n e.preventDefault();\n\n if (!$(this).hasClass('badge-success')) {\n if ($(this).hasClass('badge-secondary')) {\n $(this).removeClass('badge-secondary');\n $(this).addClass('badge-info');\n } else {\n $(this).addClass('badge-secondary');\n $(this).removeClass('badge-info');\n }\n }\n });\n\n $('.detail-scaleoption').click(function(e) {\n e.preventDefault();\n\n let row = $(this).parents('[data-region=\"detailed-rating\"]');\n let value = $(this).find('.detail-scaleoptionlabel').data(\"value\");\n setComment(row, '.student-comment', value, true);\n });\n\n $('.detail-scaleoptionlabel').hover(function(e) {\n e.preventDefault();\n\n if (!$(this).hasClass('badge-success')) {\n if ($(this).hasClass('badge-secondary')) {\n $(this).removeClass('badge-secondary');\n $(this).addClass('badge-info');\n } else {\n $(this).addClass('badge-secondary');\n $(this).removeClass('badge-info');\n }\n }\n });\n\n $(\"#save-feedback\").click(function() {\n saveResponses(false);\n });\n\n $(\"#submit-feedback\").click(function() {\n saveResponses(true);\n });\n\n $(\".btn-detail-rating\").click(function(e) {\n e.preventDefault();\n let row = $(this).parents('[data-region=\"question-row\"]');\n let detailedRating = row.find(\".detailed-rating\");\n if(detailedRating.hasClass(\"hidden\")) {\n detailedRating.removeClass(\"hidden\");\n $(this).html(\"−\");\n } else {\n detailedRating.addClass(\"hidden\");\n $(this).html(\"+\");\n }\n\n });\n };\n\n /**\n * Save the responses.\n *\n * @param {boolean} finalise\n */\n function saveResponses(finalise) {\n\n $('.student-comment').each(function() {\n let row = $(this).parents('[data-region=\"question-row\"]');\n responses[row.data('criterionid')]['studentcomment'] = getComment(row,'.student-comment');\n });\n $('.private-comment').each(function() {\n let row = $(this).parents('[data-region=\"question-row\"]');\n responses[row.data('criterionid')]['privatecomment'] = getComment(row, '.private-comment');\n });\n\n let questionnaireTable = $('[data-region=\"questionnaire\"]');\n let toUser = questionnaireTable.data('touserid');\n let toUserFullname = questionnaireTable.data('tousername');\n let verbalfeedbackId = questionnaireTable.data('verbalfeedbackid');\n let submissionId = questionnaireTable.data('submissionid');\n let anonymous = questionnaireTable.data('anonymous');\n\n if (anonymous && finalise) {\n // Show confirmation dialogue to anonymise the feedback responses.\n let messageStrings = [\n {\n key: 'finaliseanonymousfeedback',\n component: 'mod_verbalfeedback'\n },\n {\n key: 'confirmfinaliseanonymousfeedback',\n component: 'mod_verbalfeedback',\n param: {\n 'name': toUserFullname\n }\n }\n ];\n\n Str.get_strings(messageStrings, 'mod_verbalfeedback').done(function(messages) {\n showConfirmationDialogue(messages[0], messages[1], verbalfeedbackId, submissionId, toUser, responses, finalise);\n }).fail(Notification.exception);\n } else {\n // Just save the responses.\n submitResponses(verbalfeedbackId, submissionId, toUser, responses, finalise);\n }\n }\n\n /**\n * Send the responses to the server.\n *\n * @param {number} verbalfeedbackId\n * @param {number} submissionId\n * @param {number} toUser\n * @param {array} responses\n * @param {boolean} finalise\n */\n function submitResponses(verbalfeedbackId, submissionId, toUser, responses, finalise) {\n let responseObjects = [];\n for (const tuple of Object.entries(responses)) {\n if (tuple[1] !== null) {\n responseObjects.push(tuple[1]);\n }\n }\n\n let promises = Ajax.call([\n {\n methodname: 'mod_verbalfeedback_save_responses',\n args: {\n verbalfeedbackid: verbalfeedbackId,\n submissionid: submissionId,\n touserid: toUser,\n responses: responseObjects,\n complete: finalise\n }\n }\n ]);\n\n promises[0].done(function(response) {\n // console.log(response);\n let messageStrings = [\n {\n key: 'responsessaved',\n component: 'mod_verbalfeedback'\n },\n {\n key: 'errorresponsesavefailed',\n component: 'mod_verbalfeedback'\n }\n ];\n\n Str.get_strings(messageStrings).done(function(messages) {\n let notificationData = {};\n if (response.result) {\n notificationData.message = messages[0];\n notificationData.type = \"success\";\n } else {\n notificationData.message = messages[1];\n notificationData.type = \"error\";\n }\n Notification.addNotification(notificationData);\n }).fail(Notification.exception);\n\n window.location = response.redirurl;\n }).fail(Notification.exception);\n }\n\n /**\n * Renders the confirmation dialogue to submit and finalise the responses.\n *\n * @param {string} title\n * @param {string} confirmationMessage\n * @param {number} verbalfeedbackId\n * @param {number} submissionId\n * @param {number} toUser\n * @param {Array} responses\n * @param {boolean} finalise\n */\n function showConfirmationDialogue(title, confirmationMessage, verbalfeedbackId, submissionId, toUser, responses, finalise) {\n let confirmButtonTextPromise = Str.get_string('finalise', 'mod_verbalfeedback');\n let confirmModalPromise = ModalFactory.create({\n title: title,\n body: confirmationMessage,\n large: true,\n type: ModalFactory.types.SAVE_CANCEL\n });\n $.when(confirmButtonTextPromise, confirmModalPromise).done(function(confirmButtonText, modal) {\n modal.setSaveButtonText(confirmButtonText);\n\n // Display the dialogue.\n modal.show();\n\n // On hide handler.\n modal.getRoot().on(ModalEvents.hidden, function() {\n // Empty modal contents when it's hidden.\n modal.setBody('');\n });\n\n modal.getRoot().on(ModalEvents.save, function() {\n submitResponses(verbalfeedbackId, submissionId, toUser, responses, finalise);\n });\n });\n\n }\n\n return questionnaire;\n});\n"],"names":["define","$","Templates","Notification","Ajax","Str","ModalFactory","ModalEvents","responses","editor","getEditor","length","window","tinyMCE","setComment","row","classSelector","comment","append","editorcontent","find","html","commentId","attr","$input","get","insertContent","setContent","oldComment","val","trim","getComment","classSel","replace","getContent","questionnaire","registerEvents","each","this","data","criterionid","value","studentcomment","privatecomment","questionnaireTable","fromUser","toUser","verbalfeedbackId","submissionId","call","methodname","args","verbalfeedbackid","fromuserid","touserid","submissionid","done","result","response","options","selected","removeClass","addClass","fail","exception","saveResponses","finalise","parents","toUserFullname","messageStrings","key","component","param","get_strings","messages","title","confirmationMessage","confirmButtonTextPromise","get_string","confirmModalPromise","create","body","large","type","types","SAVE_CANCEL","when","confirmButtonText","modal","setSaveButtonText","show","getRoot","on","hidden","setBody","save","submitResponses","showConfirmationDialogue","responseObjects","tuple","Object","entries","push","complete","notificationData","message","addNotification","location","redirurl","prototype","click","e","preventDefault","hasClass","forId","removeAttr","hover","detailedRating"],"mappings":";;;;;;;;AAuBAA,0CAAO,CAAC,SACJ,iBACA,oBACA,YACA,WACA,qBACA,sBACD,SAASC,EAAGC,UAAWC,aAAcC,KAAMC,IAAKC,aAAcC,iBAEzDC,UAAY,OAEZC,aACEC,UAAY,kBACVD,SAIAA,OADAR,EAAE,gBAAgBU,OAAS,EAClB,OACFC,OAAOC,QACL,OAEA,WAENJ,SAGLK,WAAa,SAASC,IAAKC,cAAeC,aAASC,kEACjC,SAAhBR,YAAwB,OAClBS,cAAgBJ,IAAIK,KAAKJ,cAAgB,+BAC3CE,YACAC,cAAcD,OAAO,WAAaD,QAAU,mBAGhDE,cAAcE,KAAKJ,eAGjBK,UAAYP,IAAIK,KAAKJ,eAAeO,KAAK,SAC3CD,UAAW,OACLE,OAASvB,EAAE,IAAMqB,cACH,SAAhBZ,mBACIQ,YACAN,OAAOC,QAAQY,IAAIH,WAAWI,cAAc,WAAaT,QAAU,wBAGvEL,OAAOC,QAAQY,IAAIH,WAAWK,WAAWV,YAGzCC,OAAQ,OACFU,WAAaJ,OAAOK,SACA,KAAtBD,WAAWE,mBACXN,OAAOK,IAAID,WAAa,SAAWX,SAI3CO,OAAOK,IAAIZ,WAIbc,WAAa,SAAUhB,IAAKiB,aACV,SAAhBtB,YAAwB,KACpBO,QAAUF,IAAIK,KAAKY,SAAW,wBAAwBX,aACT,KAA1CJ,QAAQgB,QAAQ,WAAW,IAAIH,OAAgB,GAAKb,cAEzDK,UAAYP,IAAIK,KAAKY,UAAUT,KAAK,SACtCD,UAAW,KACPL,QAAU,UAEVA,QADgB,SAAhBP,YACUE,OAAOC,QAAQY,IAAIH,WAAWY,aAE9BjC,EAAE,IAAMqB,WAAWO,MAEgB,KAA1CZ,QAAQgB,QAAQ,WAAW,IAAIH,OAAgB,GAAKb,cAExD,QAGPkB,cAAgB,gBACXC,iBAGLnC,EAAE,gCAAgCoC,MAAK,WACnC7B,UAAUP,EAAEqC,MAAMC,KAAK,gBAAkB,CACrCC,YAAavC,EAAEqC,MAAMC,KAAK,eAC1BE,MAAO,KACPC,eAAgB,GAChBC,eAAgB,WAIpBC,mBAAqB3C,EAAE,oCAEc,GAAtC2C,mBAAmBL,KAAK,sBAKvBM,SAAWD,mBAAmBL,KAAK,cACnCO,OAASF,mBAAmBL,KAAK,YACjCQ,iBAAmBH,mBAAmBL,KAAK,oBAC3CS,aAAeJ,mBAAmBL,KAAK,gBAE5BnC,KAAK6C,KAAK,CACrB,CACIC,WAAY,mCACZC,KAAM,CACFC,iBAAkBL,iBAClBM,WAAYR,SACZS,SAAUR,OACVS,aAAcP,iBAKjB,GAAGQ,MAAK,SAASC,QACtBxD,EAAEoC,KAAKoB,OAAOjD,WAAW,eACnBkD,SAAWpB,KACb9B,UAAUkD,SAASlB,aAAnB,YAAiDkB,SAASlB,YAC1DhC,UAAUkD,SAASlB,aAAnB,MAA2CkB,SAASjB,MACpDjC,UAAUkD,SAASlB,aAAnB,eAAoDkB,SAAShB,eAC7DlC,UAAUkD,SAASlB,aAAnB,eAAoDkB,SAASf,eAE7D1C,EAAE,gCAAgCoC,MAAK,cAC/BpC,EAAEqC,MAAMC,KAAK,iBAAmBmB,SAASlB,YAAa,KACpDmB,QAAU1D,EAAEqC,MAAMlB,KAAK,gBACrBuC,SACAA,QAAQtB,MAAK,eAELuB,SAAW3D,EAAEqC,MAAMlB,KAAK,UACxBwC,SAASrB,KAAK,WAAamB,SAASjB,OAIF,KAA3BmB,SAASrB,KAAK,UAAsC,OAAnBmB,SAASjB,SAHjDmB,SAASC,YAAY,mBACrBD,SAASC,YAAY,cACrBD,SAASE,SAAS,qBAQE,KAA5BJ,SAAShB,gBACT5B,WAAWb,EAAEqC,MAAO,mBAAoBoB,SAAShB,gBAErB,KAA5BgB,SAASf,gBACT7B,WAAWb,EAAEqC,MAAO,mBAAoBoB,SAASf,0BAKlEoB,KAAK5D,aAAa6D,qBAyGhBC,cAAcC,UAEnBjE,EAAE,oBAAoBoC,MAAK,eACnBtB,IAAMd,EAAEqC,MAAM6B,QAAQ,gCAC1B3D,UAAUO,IAAIwB,KAAK,gBAAnB,eAAuDR,WAAWhB,IAAI,uBAE1Ed,EAAE,oBAAoBoC,MAAK,eACnBtB,IAAMd,EAAEqC,MAAM6B,QAAQ,gCAC1B3D,UAAUO,IAAIwB,KAAK,gBAAnB,eAAuDR,WAAWhB,IAAK,2BAGvE6B,mBAAqB3C,EAAE,iCACvB6C,OAASF,mBAAmBL,KAAK,YACjC6B,eAAiBxB,mBAAmBL,KAAK,cACzCQ,iBAAmBH,mBAAmBL,KAAK,oBAC3CS,aAAeJ,mBAAmBL,KAAK,mBAC3BK,mBAAmBL,KAAK,cAEvB2B,SAAU,KAEnBG,eAAiB,CACjB,CACIC,IAAK,4BACLC,UAAW,sBAEf,CACID,IAAK,mCACLC,UAAW,qBACXC,MAAO,MACKJ,kBAKpB/D,IAAIoE,YAAYJ,eAAgB,sBAAsBb,MAAK,SAASkB,oBA+E1CC,MAAOC,oBAAqB7B,iBAAkBC,aAAcF,OAAQtC,UAAW0D,cAC3GW,yBAA2BxE,IAAIyE,WAAW,WAAY,sBACpDC,oBAAsBzE,aAAa0E,OAAO,CAC1CL,MAAOA,MACPM,KAAML,oBACNM,OAAO,EACPC,KAAM7E,aAAa8E,MAAMC,cAE7BpF,EAAEqF,KAAKT,yBAA0BE,qBAAqBvB,MAAK,SAAS+B,kBAAmBC,OACnFA,MAAMC,kBAAkBF,mBAGxBC,MAAME,OAGNF,MAAMG,UAAUC,GAAGrF,YAAYsF,QAAQ,WAEnCL,MAAMM,QAAQ,OAGlBN,MAAMG,UAAUC,GAAGrF,YAAYwF,MAAM,WACjCC,gBAAgBjD,iBAAkBC,aAAcF,OAAQtC,UAAW0D,gBAnGnE+B,CAAyBvB,SAAS,GAAIA,SAAS,GAAI3B,iBAAkBC,aAAcF,OAAQtC,UAAW0D,aACvGH,KAAK5D,aAAa6D,gBAGrBgC,gBAAgBjD,iBAAkBC,aAAcF,OAAQtC,UAAW0D,mBAalE8B,gBAAgBjD,iBAAkBC,aAAcF,OAAQtC,UAAW0D,cACpEgC,gBAAkB,OACjB,MAAMC,SAASC,OAAOC,QAAQ7F,WAChB,OAAb2F,MAAM,IACRD,gBAAgBI,KAAKH,MAAM,IAIhB/F,KAAK6C,KAAK,CACrB,CACIC,WAAY,oCACZC,KAAM,CACFC,iBAAkBL,iBAClBQ,aAAcP,aACdM,SAAUR,OACVtC,UAAW0F,gBACXK,SAAUrC,aAKb,GAAGV,MAAK,SAASE,UAatBrD,IAAIoE,YAXe,CACf,CACIH,IAAK,iBACLC,UAAW,sBAEf,CACID,IAAK,0BACLC,UAAW,wBAIaf,MAAK,SAASkB,cACxC8B,iBAAmB,GACjB9C,SAASD,QACT+C,iBAAiBC,QAAU/B,SAAS,GACpC8B,iBAAiBrB,KAAO,YAExBqB,iBAAiBC,QAAU/B,SAAS,GACpC8B,iBAAiBrB,KAAO,SAE5BhF,aAAauG,gBAAgBF,qBAC9BzC,KAAK5D,aAAa6D,WAErBpD,OAAO+F,SAAWjD,SAASkD,YAC5B7C,KAAK5D,aAAa6D,kBAzMzB7B,cAAc0E,UAAUzE,eAAiB,WACrCnC,EAAE,gBAAgB6G,OAAM,SAASC,GAC7BA,EAAEC,qBAEEjG,IAAMd,EAAEqC,MAAM6B,QAAQ,gCACtBR,QAAU5C,IAAIK,KAAK,SAGvBnB,EAAEoC,KAAKsB,SAAS,cACR1D,EAAEqC,MAAM2E,SAAS,iBAAkB,CACnChH,EAAEqC,MAAMuB,YAAY,iBACpB5D,EAAEqC,MAAMwB,SAAS,uBAEboD,MAAQjH,EAAEqC,MAAMf,KAAK,OACPtB,EAAE,IAAMiH,OACdC,WAAW,mBAK3BvD,SAAW3D,EAAEqC,MAAMlB,KAAK,SAC5BwC,SAASC,YAAY,mBACrBD,SAASC,YAAY,cACrBD,SAASE,SAAS,iBAGN7D,EAAE,IAAM2D,SAASrC,KAAK,QAC5BA,KAAK,UAAW,eAClBiB,YAAczB,IAAIwB,KAAK,eAGI,KAA3BqB,SAASrB,KAAK,SACd/B,UAAUgC,aAAV,MAAkC,KAElChC,UAAUgC,aAAV,MAAkCoB,SAASrB,KAAK,YAIxDtC,EAAE,qBAAqBmH,OAAM,SAASL,GAClCA,EAAEC,iBAEG/G,EAAEqC,MAAM2E,SAAS,mBACdhH,EAAEqC,MAAM2E,SAAS,oBACjBhH,EAAEqC,MAAMuB,YAAY,mBACpB5D,EAAEqC,MAAMwB,SAAS,gBAEjB7D,EAAEqC,MAAMwB,SAAS,mBACjB7D,EAAEqC,MAAMuB,YAAY,mBAKhC5D,EAAE,uBAAuB6G,OAAM,SAASC,GACpCA,EAAEC,qBAEEjG,IAAMd,EAAEqC,MAAM6B,QAAQ,mCACtB1B,MAAQxC,EAAEqC,MAAMlB,KAAK,4BAA4BmB,KAAK,SAC1DzB,WAAWC,IAAK,mBAAoB0B,OAAO,MAG/CxC,EAAE,4BAA4BmH,OAAM,SAASL,GACzCA,EAAEC,iBAEG/G,EAAEqC,MAAM2E,SAAS,mBACdhH,EAAEqC,MAAM2E,SAAS,oBACjBhH,EAAEqC,MAAMuB,YAAY,mBACpB5D,EAAEqC,MAAMwB,SAAS,gBAEjB7D,EAAEqC,MAAMwB,SAAS,mBACjB7D,EAAEqC,MAAMuB,YAAY,mBAKhC5D,EAAE,kBAAkB6G,OAAM,WACtB7C,eAAc,MAGlBhE,EAAE,oBAAoB6G,OAAM,WACxB7C,eAAc,MAGlBhE,EAAE,sBAAsB6G,OAAM,SAASC,GACnCA,EAAEC,qBAEEK,eADMpH,EAAEqC,MAAM6B,QAAQ,gCACD/C,KAAK,oBAC3BiG,eAAeJ,SAAS,WACvBI,eAAexD,YAAY,UAC3B5D,EAAEqC,MAAMjB,KAAK,OAEbgG,eAAevD,SAAS,UACxB7D,EAAEqC,MAAMjB,KAAK,UAuJlBc"}
\ No newline at end of file
diff --git a/amd/src/questionnaire.js b/amd/src/questionnaire.js
index 29b2754..9f90320 100644
--- a/amd/src/questionnaire.js
+++ b/amd/src/questionnaire.js
@@ -31,6 +31,72 @@ define(['jquery',
], function($, Templates, Notification, Ajax, Str, ModalFactory, ModalEvents) {
var responses = [];
+
+ let editor;
+ const getEditor = function() {
+ if (editor) {
+ return editor;
+ }
+ if ($('.editor_atto').length > 0) {
+ editor = 'atto';
+ } else if (window.tinyMCE) {
+ editor = 'tiny';
+ } else {
+ editor = 'textarea';
+ }
+ return editor;
+ };
+
+ const setComment = function(row, classSelector, comment, append = false) {
+ if (getEditor() === 'atto') {
+ const editorcontent = row.find(classSelector + '.editor_atto_content');
+ if (append) {
+ editorcontent.append("");
+ return;
+ }
+ editorcontent.html(comment);
+ return;
+ }
+ const commentId = row.find(classSelector).attr('id');
+ if (commentId) {
+ const $input = $('#' + commentId);
+ if (getEditor() === 'tiny') {
+ if (append) {
+ window.tinyMCE.get(commentId).insertContent('
');
+ return;
+ }
+ window.tinyMCE.get(commentId).setContent(comment);
+ return;
+ }
+ if (append) {
+ const oldComment = $input.val();
+ if (oldComment.trim() !== '') {
+ $input.val(oldComment + "\n\n" + comment);
+ return;
+ }
+ }
+ $input.val(comment);
+ }
+ };
+
+ const getComment = function (row, classSel) {
+ if (getEditor() === 'atto') {
+ let comment = row.find(classSel + '.editor_atto_content').html();
+ return comment.replace(/<[^>]+>/g,'').trim() === '' ? '' : comment; // drop empty comments
+ }
+ const commentId = row.find(classSel).attr('id');
+ if (commentId) {
+ let comment = '';
+ if (getEditor() === 'tiny') {
+ comment = window.tinyMCE.get(commentId).getContent();
+ } else {
+ comment = $('#' + commentId).val();
+ }
+ return comment.replace(/<[^>]+>/g,'').trim() === '' ? '' : comment; // drop empty comments
+ }
+ return '';
+ };
+
var questionnaire = function() {
this.registerEvents();
@@ -94,13 +160,11 @@ define(['jquery',
}
});
}
- let studentcomment = $(this).find('.student-comment.editor_atto_content');
- if (studentcomment && response.studentcomment !== '') {
- studentcomment.html(response.studentcomment);
+ if (response.studentcomment !== '') {
+ setComment($(this), '.student-comment', response.studentcomment);
}
- let privatecomment = $(this).find('.private-comment.editor_atto_content');
- if (privatecomment && response.privatecomment !== '') {
- privatecomment.html(response.privatecomment);
+ if (response.privatecomment !== '') {
+ setComment($(this), '.private-comment', response.privatecomment);
}
}
});
@@ -138,8 +202,6 @@ define(['jquery',
radio.attr('checked', 'checked');
let criterionid = row.data('criterionid');
-
-
// Add this selected value to the array of responses.
if (selected.data('value') === "") { // === is necessary because == "0" equals true;
responses[criterionid]['value'] = null;
@@ -167,9 +229,7 @@ define(['jquery',
let row = $(this).parents('[data-region="detailed-rating"]');
let value = $(this).find('.detail-scaleoptionlabel').data("value");
- let studentComment = row.find('.student-comment.editor_atto_content');
- studentComment.append("");
-
+ setComment(row, '.student-comment', value, true);
});
$('.detail-scaleoptionlabel').hover(function(e) {
@@ -218,15 +278,11 @@ define(['jquery',
$('.student-comment').each(function() {
let row = $(this).parents('[data-region="question-row"]');
- let comment = row.find('.student-comment.editor_atto_content').html();
- comment = (comment == "
" ? "": comment); // drop empty comments
- responses[row.data('criterionid')]['studentcomment'] = comment;
+ responses[row.data('criterionid')]['studentcomment'] = getComment(row,'.student-comment');
});
$('.private-comment').each(function() {
let row = $(this).parents('[data-region="question-row"]');
- let comment = row.find('.private-comment.editor_atto_content').html();
- comment = (comment == "
" ? "": comment); // drop empty comments
- responses[row.data('criterionid')]['privatecomment'] = comment;
+ responses[row.data('criterionid')]['privatecomment'] = getComment(row, '.private-comment');
});
let questionnaireTable = $('[data-region="questionnaire"]');
diff --git a/backup/moodle2/backup_verbalfeedback_stepslib.php b/backup/moodle2/backup_verbalfeedback_stepslib.php
index 3dc9e9f..95b78f2 100644
--- a/backup/moodle2/backup_verbalfeedback_stepslib.php
+++ b/backup/moodle2/backup_verbalfeedback_stepslib.php
@@ -49,7 +49,7 @@ protected function define_structure() {
$instance = new backup_nested_element('instance', ['id'], [
'templateid', 'course', 'name', 'intro',
'introformat', 'grade', 'status', 'timeopen',
- 'timeclose', 'timemodified', 'releasetype', 'released']);
+ 'timeclose', 'timemodified', 'releasetype', 'released', ]);
$languages = new backup_nested_element('languages');
$language = new backup_nested_element('language', ['id'], ['language']);
@@ -90,11 +90,11 @@ protected function define_structure() {
$submissions = new backup_nested_element('submissions');
$submission = new backup_nested_element('submission', ['id'], ['instanceid', 'fromuserid', 'touserid', 'status',
- 'remarks']);
+ 'remarks', ]);
$responses = new backup_nested_element('responses');
$response = new backup_nested_element('response', ['id'], ['instanceid', 'submissionid', 'criterionid',
- 'fromuserid', 'touserid', 'value', 'studentcomment', 'privatecomment']);
+ 'fromuserid', 'touserid', 'value', 'studentcomment', 'privatecomment', ]);
// Build the tree.
@@ -145,25 +145,25 @@ protected function define_structure() {
$category->set_source_table(tables::INSTANCE_CATEGORY_TABLE, ['instanceid' => backup::VAR_PARENTID], 'id ASC');
$categoryheader->set_source_table(tables::LOCALIZED_STRING_TABLE, ['foreignkey' => backup::VAR_PARENTID,
- 'type' => backup_helper::is_sqlparam(localized_string_type::INSTANCE_CATEGORY_HEADER)]);
+ 'type' => backup_helper::is_sqlparam(localized_string_type::INSTANCE_CATEGORY_HEADER), ]);
$criterion->set_source_table(tables::INSTANCE_CRITERION_TABLE, ['categoryid' => backup::VAR_PARENTID], 'id ASC');
$criteriontext->set_source_table(tables::LOCALIZED_STRING_TABLE, ['foreignkey' => backup::VAR_PARENTID,
- 'type' => backup_helper::is_sqlparam(localized_string_type::INSTANCE_CRITERION)]);
+ 'type' => backup_helper::is_sqlparam(localized_string_type::INSTANCE_CRITERION), ]);
$subrating->set_source_table(tables::INSTANCE_SUBRATING_TABLE, ['criterionid' => backup::VAR_PARENTID], 'id ASC');
$subratingtitle->set_source_table(tables::LOCALIZED_STRING_TABLE, ['foreignkey' => backup::VAR_PARENTID,
- 'type' => backup_helper::is_sqlparam(localized_string_type::INSTANCE_SUBRATING_TITLE)]);
+ 'type' => backup_helper::is_sqlparam(localized_string_type::INSTANCE_SUBRATING_TITLE), ]);
$subratingdescription->set_source_table(tables::LOCALIZED_STRING_TABLE, ['foreignkey' => backup::VAR_PARENTID,
- 'type' => backup_helper::is_sqlparam(localized_string_type::INSTANCE_SUBRATING_DESCRIPTION)]);
+ 'type' => backup_helper::is_sqlparam(localized_string_type::INSTANCE_SUBRATING_DESCRIPTION), ]);
$subratingverynegative->set_source_table(tables::LOCALIZED_STRING_TABLE, ['foreignkey' => backup::VAR_PARENTID,
- 'type' => backup_helper::is_sqlparam(localized_string_type::INSTANCE_SUBRATING_VERY_NEGATIVE)]);
+ 'type' => backup_helper::is_sqlparam(localized_string_type::INSTANCE_SUBRATING_VERY_NEGATIVE), ]);
$subratingnegative->set_source_table(tables::LOCALIZED_STRING_TABLE, ['foreignkey' => backup::VAR_PARENTID,
- 'type' => backup_helper::is_sqlparam(localized_string_type::INSTANCE_SUBRATING_NEGATIVE)]);
+ 'type' => backup_helper::is_sqlparam(localized_string_type::INSTANCE_SUBRATING_NEGATIVE), ]);
$subratingpositive->set_source_table(tables::LOCALIZED_STRING_TABLE, ['foreignkey' => backup::VAR_PARENTID,
- 'type' => backup_helper::is_sqlparam(localized_string_type::INSTANCE_SUBRATING_POSITIVE)]);
+ 'type' => backup_helper::is_sqlparam(localized_string_type::INSTANCE_SUBRATING_POSITIVE), ]);
$subratingverypositive->set_source_table(tables::LOCALIZED_STRING_TABLE, ['foreignkey' => backup::VAR_PARENTID,
- 'type' => backup_helper::is_sqlparam(localized_string_type::INSTANCE_SUBRATING_VERY_POSITIVE)]);
+ 'type' => backup_helper::is_sqlparam(localized_string_type::INSTANCE_SUBRATING_VERY_POSITIVE), ]);
// All the rest of elements only happen if we are including user info.
if ($userinfo) {
diff --git a/backup/moodle2/restore_verbalfeedback_activity_task.class.php b/backup/moodle2/restore_verbalfeedback_activity_task.class.php
index b7d07cf..d0afb06 100644
--- a/backup/moodle2/restore_verbalfeedback_activity_task.class.php
+++ b/backup/moodle2/restore_verbalfeedback_activity_task.class.php
@@ -54,9 +54,9 @@ protected function define_my_steps() {
* Define the contents in the activity that must be processed by the link decoder
*/
public static function define_decode_contents() {
- $contents = array();
+ $contents = [];
- $contents[] = new restore_decode_content('verbalfeedback', array('intro'), 'verbalfeedback');
+ $contents[] = new restore_decode_content('verbalfeedback', ['intro'], 'verbalfeedback');
return $contents;
}
@@ -65,7 +65,7 @@ public static function define_decode_contents() {
* Define the decoding rules for links belonging to the activity to be executed by the link decoder
*/
public static function define_decode_rules() {
- $rules = array();
+ $rules = [];
$rules[] = new restore_decode_rule('VERBALFEEDBACKVIEWBYID', '/mod/verbalfeedback/view.php?id=$1', 'course_module');
$rules[] = new restore_decode_rule('VERBALFEEDBACKINDEX', '/mod/verbalfeedback/index.php?id=$1', 'course');
@@ -81,7 +81,7 @@ public static function define_decode_rules() {
* of {@see restore_log_rule} objects
*/
public static function define_restore_log_rules() {
- $rules = array();
+ $rules = [];
$rules[] = new restore_log_rule('verbalfeedback', 'add', 'view.php?id={course_module}', '{verbalfeedback}');
$rules[] = new restore_log_rule('verbalfeedback', 'update', 'view.php?id={course_module}', '{verbalfeedback}');
@@ -104,7 +104,7 @@ public static function define_restore_log_rules() {
* activity level. All them are rules not linked to any module instance (cmid = 0)
*/
public static function define_restore_log_rules_for_course() {
- $rules = array();
+ $rules = [];
// Fix old wrong uses (missing extension).
$rules[] = new restore_log_rule('verbalfeedback', 'view all', 'index?id={course}', null,
diff --git a/backup/moodle2/restore_verbalfeedback_stepslib.php b/backup/moodle2/restore_verbalfeedback_stepslib.php
index b010872..e28fc1b 100644
--- a/backup/moodle2/restore_verbalfeedback_stepslib.php
+++ b/backup/moodle2/restore_verbalfeedback_stepslib.php
@@ -41,7 +41,7 @@ class restore_verbalfeedback_activity_structure_step extends restore_activity_st
*/
protected function define_structure() {
- $paths = array();
+ $paths = [];
$userinfo = $this->get_setting_value('userinfo');
// Using 'instance' instead of 'verbalfeedback' does not work here.
diff --git a/classes/api.php b/classes/api.php
index 2c2d0a6..7c15355 100644
--- a/classes/api.php
+++ b/classes/api.php
@@ -114,7 +114,7 @@ public static function get_instance_by_itemid($itemid) {
"} WHERE id = (SELECT categoryid
FROM {" . tables::INSTANCE_CRITERION_TABLE .
"} WHERE id = ?))",
- array($itemid), IGNORE_MISSING);
+ [$itemid], IGNORE_MISSING);
}
/**
@@ -131,7 +131,7 @@ public static function get_instance_by_categoryid($categoryid) {
"} WHERE id = (SELECT instanceid
FROM {" . tables::INSTANCE_CATEGORY_TABLE .
"} WHERE id = ?)",
- array($categoryid), IGNORE_MISSING);
+ [$categoryid], IGNORE_MISSING);
}
/**
@@ -224,11 +224,11 @@ public static function generate_verbalfeedback_feedback_states($verbalfeedbackid
FROM {verbalfeedback_submission} fs
WHERE fs.instanceid = f.id
AND fs.fromuserid = :fromuser2
- )'
+ )',
];
$params = [
'verbalfeedbackid' => $verbalfeedbackid,
- 'fromuser2' => $fromuserid
+ 'fromuser2' => $fromuserid,
];
if (!$includeself) {
diff --git a/classes/external.php b/classes/external.php
index 2f4765a..3ce99cf 100644
--- a/classes/external.php
+++ b/classes/external.php
@@ -67,7 +67,7 @@ public static function get_questions($verbalfeedbackid) {
throw new moodle_exception(json_encode($preparedcats));
return [
'items' => $preparedcats,
- 'warnings' => $warnings
+ 'warnings' => $warnings,
];
}
@@ -98,11 +98,11 @@ public static function get_questions_returns() {
'type' => new external_value(PARAM_INT, 'The question type.'),
'typeName' => new external_value(PARAM_TEXT, 'The question type text value.'),
'category' => new external_value(PARAM_INT, 'The question category.'),
- 'categoryName' => new external_value(PARAM_TEXT, 'The question category text value.')
+ 'categoryName' => new external_value(PARAM_TEXT, 'The question category text value.'),
]
)
),
- 'warnings' => new external_warnings()
+ 'warnings' => new external_warnings(),
]
);
}
@@ -146,7 +146,7 @@ public static function add_question($question, $type, $category, $verbalfeedback
return [
'questionid' => $questionid,
- 'warnings' => $warnings
+ 'warnings' => $warnings,
];
}
@@ -173,7 +173,7 @@ public static function add_question_returns() {
return new external_single_structure(
[
'questionid' => new external_value(PARAM_INT, 'The question ID of the added question.'),
- 'warnings' => new external_warnings()
+ 'warnings' => new external_warnings(),
]
);
}
@@ -222,7 +222,7 @@ public static function update_question($id, $question, $type, $category, $verbal
return [
'result' => $result,
- 'warnings' => $warnings
+ 'warnings' => $warnings,
];
}
@@ -250,7 +250,7 @@ public static function update_question_returns() {
return new external_single_structure(
[
'result' => new external_value(PARAM_BOOL, 'The question update processing result.'),
- 'warnings' => new external_warnings()
+ 'warnings' => new external_warnings(),
]
);
}
@@ -291,17 +291,15 @@ public static function delete_question_returns() {
return new external_single_structure(
[
'result' => new external_value(PARAM_BOOL, 'The question update processing result.'),
- 'warnings' => new external_warnings()
+ 'warnings' => new external_warnings(),
]
);
}
/**
-<<<<<<< HEAD
-=======
* Fetches the questions assigned to a verbal feedback instance.
*
- * @param int $verbalfeedbackid The verbalf eedback ID.
+ * @param int $verbalfeedbackid The verbalfeedback ID.
* @return array
* @throws coding_exception
* @throws dml_exception
@@ -323,7 +321,7 @@ public static function get_items($verbalfeedbackid) {
require_capability('mod/verbalfeedback:can_respond', $context);
return [
'items' => $preparedcats,
- 'warnings' => $warnings
+ 'warnings' => $warnings,
];
}
@@ -335,7 +333,7 @@ public static function get_items($verbalfeedbackid) {
public static function get_items_parameters() {
return new external_function_parameters(
[
- 'verbalfeedbackid' => new external_value(PARAM_INT, 'The verbal feedback ID.')
+ 'verbalfeedbackid' => new external_value(PARAM_INT, 'The verbal feedback ID.'),
]
);
}
@@ -364,14 +362,14 @@ public static function get_items_returns() {
'type' => new external_value(PARAM_INT, 'The question type.'),
'typetext' => new external_value(PARAM_TEXT, 'The question type text value.'),
'category' => new external_value(PARAM_INT, 'The question category.'),
- 'categorytext' => new external_value(PARAM_TEXT, 'The question category text value.')
+ 'categorytext' => new external_value(PARAM_TEXT, 'The question category text value.'),
]
)
- )
+ ),
]
)
),
- 'warnings' => new external_warnings()
+ 'warnings' => new external_warnings(),
]
);
}
@@ -393,7 +391,7 @@ public static function set_items($verbalfeedbackid, $questionids) {
$warnings = [];
$params = external_api::validate_parameters(self::set_items_parameters(), [
'verbalfeedbackid' => $verbalfeedbackid,
- 'questionids' => $questionids
+ 'questionids' => $questionids,
]);
// Validate context and capability.
@@ -408,7 +406,7 @@ public static function set_items($verbalfeedbackid, $questionids) {
return [
'result' => $result,
- 'warnings' => $warnings
+ 'warnings' => $warnings,
];
}
@@ -423,7 +421,7 @@ public static function set_items_parameters() {
'verbalfeedbackid' => new external_value(PARAM_INT, 'The verbal feedback ID.'),
'questionids' => new external_multiple_structure(
new external_value(PARAM_INT, 'The question ID.')
- )
+ ),
]
);
}
@@ -437,13 +435,12 @@ public static function set_items_returns() {
return new external_single_structure(
[
'result' => new external_value(PARAM_BOOL, 'The processing result.'),
- 'warnings' => new external_warnings()
+ 'warnings' => new external_warnings(),
]
);
}
/**
->>>>>>> 6b7e2bb (Adding capabilities check in external.php fixes #4.)
* Parameter description for update_item_multiplier().
*
* @return external_function_parameters
@@ -451,7 +448,7 @@ public static function set_items_returns() {
public static function update_item_multiplier_parameters() {
return new external_function_parameters([
'itemid' => new external_value(PARAM_INT, 'The id of the item.'),
- 'multiplier' => new external_value(PARAM_FLOAT, 'The new multiplier value.')
+ 'multiplier' => new external_value(PARAM_FLOAT, 'The new multiplier value.'),
]);
}
@@ -466,7 +463,7 @@ public static function update_item_multiplier($itemid, $multiplier) {
$warnings = [];
$params = external_api::validate_parameters(self::update_item_multiplier_parameters(), [
'itemid' => $itemid,
- 'multiplier' => $multiplier
+ 'multiplier' => $multiplier,
]);
// Validate context and capability.
@@ -481,7 +478,7 @@ public static function update_item_multiplier($itemid, $multiplier) {
return [
'success' => $result,
- 'warnings' => $warnings
+ 'warnings' => $warnings,
];
}
@@ -494,7 +491,7 @@ public static function update_item_multiplier_returns() {
return new external_single_structure(
[
'success' => new external_value(PARAM_BOOL, 'The success of the operation.'),
- 'warnings' => new external_warnings()
+ 'warnings' => new external_warnings(),
]
);
}
@@ -507,7 +504,7 @@ public static function update_item_multiplier_returns() {
public static function update_category_percentage_parameters() {
return new external_function_parameters([
'categoryid' => new external_value(PARAM_INT, 'The id of the item.'),
- 'percentage' => new external_value(PARAM_FLOAT, 'The new percentage value.')
+ 'percentage' => new external_value(PARAM_FLOAT, 'The new percentage value.'),
]);
}
@@ -523,7 +520,7 @@ public static function update_category_percentage($categoryid, $percentage) {
$warnings = [];
$params = external_api::validate_parameters(self::update_category_percentage_parameters(), [
'categoryid' => $categoryid,
- 'percentage' => $percentage
+ 'percentage' => $percentage,
]);
// Validate context and capability.
@@ -538,7 +535,7 @@ public static function update_category_percentage($categoryid, $percentage) {
$result = api::update_category_percentage($params['categoryid'], $params['percentage']);
return [
'success' => $result,
- 'warnings' => $warnings
+ 'warnings' => $warnings,
];
}
@@ -551,7 +548,7 @@ public static function update_category_percentage_returns() {
return new external_single_structure(
[
'success' => new external_value(PARAM_BOOL, 'The success of the operation.'),
- 'warnings' => new external_warnings()
+ 'warnings' => new external_warnings(),
]
);
}
@@ -584,7 +581,7 @@ public static function get_question_types_returns() {
new external_value(PARAM_TEXT, 'Question type.'),
'List of question types.'
),
- 'warnings' => new external_warnings()
+ 'warnings' => new external_warnings(),
]
);
}
@@ -604,7 +601,7 @@ public static function get_question_categories($verbalfeedbackid) {
*/
public static function get_question_categories_parameters() {
return new external_function_parameters([
- 'verbalfeedbackid' => new external_value(PARAM_INT, 'The verbal feedback ID.')
+ 'verbalfeedbackid' => new external_value(PARAM_INT, 'The verbal feedback ID.'),
]);
}
@@ -620,7 +617,7 @@ public static function get_question_categories_returns() {
new external_value(PARAM_TEXT, 'Question category.'),
'List of question categories.'
),
- 'warnings' => new external_warnings()
+ 'warnings' => new external_warnings(),
]
);
}
@@ -656,7 +653,7 @@ public static function delete_item($id) {
return [
'result' => $result,
- 'warnings' => $warnings
+ 'warnings' => $warnings,
];
}
@@ -668,7 +665,7 @@ public static function delete_item($id) {
public static function delete_item_parameters() {
return new external_function_parameters(
[
- 'itemid' => new external_value(PARAM_INT, 'The item ID.')
+ 'itemid' => new external_value(PARAM_INT, 'The item ID.'),
]
);
}
@@ -682,7 +679,7 @@ public static function delete_item_returns() {
return new external_single_structure(
[
'result' => new external_value(PARAM_BOOL, 'The item deletion processing result.'),
- 'warnings' => new external_warnings()
+ 'warnings' => new external_warnings(),
]
);
}
@@ -721,7 +718,7 @@ public static function move_item_up($id) {
return [
'result' => $result,
- 'warnings' => $warnings
+ 'warnings' => $warnings,
];
}
@@ -733,7 +730,7 @@ public static function move_item_up($id) {
public static function move_item_up_parameters() {
return new external_function_parameters(
[
- 'itemid' => new external_value(PARAM_INT, 'The item ID.')
+ 'itemid' => new external_value(PARAM_INT, 'The item ID.'),
]
);
}
@@ -747,7 +744,7 @@ public static function move_item_up_returns() {
return new external_single_structure(
[
'result' => new external_value(PARAM_BOOL, 'The item deletion processing result.'),
- 'warnings' => new external_warnings()
+ 'warnings' => new external_warnings(),
]
);
}
@@ -786,7 +783,7 @@ public static function move_item_down($id) {
return [
'result' => $result,
- 'warnings' => $warnings
+ 'warnings' => $warnings,
];
}
@@ -798,7 +795,7 @@ public static function move_item_down($id) {
public static function move_item_down_parameters() {
return new external_function_parameters(
[
- 'itemid' => new external_value(PARAM_INT, 'The item ID.')
+ 'itemid' => new external_value(PARAM_INT, 'The item ID.'),
]
);
}
@@ -812,7 +809,7 @@ public static function move_item_down_returns() {
return new external_single_structure(
[
'result' => new external_value(PARAM_BOOL, 'The item deletion processing result.'),
- 'warnings' => new external_warnings()
+ 'warnings' => new external_warnings(),
]
);
}
@@ -835,7 +832,7 @@ public static function decline_feedback_parameters() {
return new external_function_parameters(
[
'statusid' => new external_value(PARAM_INT, 'The submission ID.'),
- 'declinereason' => new external_value(PARAM_TEXT, 'The reason for declining the feedback request.', VALUE_DEFAULT)
+ 'declinereason' => new external_value(PARAM_TEXT, 'The reason for declining the feedback request.', VALUE_DEFAULT),
]
);
}
@@ -849,7 +846,7 @@ public static function decline_feedback_returns() {
return new external_single_structure(
[
'result' => new external_value(PARAM_BOOL, 'The item deletion processing result.'),
- 'warnings' => new external_warnings()
+ 'warnings' => new external_warnings(),
]
);
}
@@ -884,7 +881,7 @@ public static function undo_decline_returns() {
return new external_single_structure(
[
'result' => new external_value(PARAM_BOOL, 'The processing result.'),
- 'warnings' => new external_warnings()
+ 'warnings' => new external_warnings(),
]
);
}
@@ -904,7 +901,7 @@ public static function data_for_participant_list($verbalfeedbackid) {
global $PAGE, $USER;
$warnings = [];
$params = external_api::validate_parameters(self::data_for_participant_list_parameters(), [
- 'verbalfeedbackid' => $verbalfeedbackid
+ 'verbalfeedbackid' => $verbalfeedbackid,
]);
$verbalfeedbackid = $params['verbalfeedbackid'];
@@ -922,7 +919,7 @@ public static function data_for_participant_list($verbalfeedbackid) {
return [
'verbalfeedbackid' => $data->verbalfeedbackid,
'participants' => $data->participants,
- 'warnings' => $warnings
+ 'warnings' => $warnings,
];
}
@@ -966,7 +963,7 @@ public static function data_for_participant_list_returns() {
]
)
),
- 'warnings' => new external_warnings()
+ 'warnings' => new external_warnings(),
]
);
}
@@ -988,7 +985,7 @@ public static function save_responses_parameters() {
'criterionid' => new external_value(PARAM_INT, 'The criterion ID.'),
'value' => new external_value(PARAM_INT, 'The response value.', VALUE_OPTIONAL, null),
'studentcomment' => new external_value(PARAM_RAW, 'The response public comment.', VALUE_OPTIONAL, ''),
- 'privatecomment' => new external_value(PARAM_RAW, 'The response private comment.', VALUE_OPTIONAL, '')
+ 'privatecomment' => new external_value(PARAM_RAW, 'The response private comment.', VALUE_OPTIONAL, ''),
], 'item to save', VALUE_OPTIONAL
), 'item collection to save', VALUE_OPTIONAL, null
),
@@ -1030,7 +1027,7 @@ public static function save_responses($verbalfeedbackid, $submissionid, $touseri
'submissionid' => $submissionid,
'touserid' => $touserid,
'responses' => $responses,
- 'complete' => $complete
+ 'complete' => $complete,
]);
$verbalfeedbackid = $params['verbalfeedbackid'];
@@ -1051,7 +1048,7 @@ public static function save_responses($verbalfeedbackid, $submissionid, $touseri
return [
'result' => $result,
'redirurl' => $redirecturl->out(),
- 'warnings' => $warnings
+ 'warnings' => $warnings,
];
}
@@ -1065,7 +1062,7 @@ public static function save_responses_returns() {
[
'result' => new external_value(PARAM_BOOL, 'The item deletion processing result.'),
'redirurl' => new external_value(PARAM_URL, 'The redirect URL.'),
- 'warnings' => new external_warnings()
+ 'warnings' => new external_warnings(),
]
);
}
@@ -1141,7 +1138,7 @@ public static function get_responses($verbalfeedbackid, $fromuserid, $touserid,
return [
'responses' => $responses,
'redirurl' => $redirecturl->out(),
- 'warnings' => $warnings
+ 'warnings' => $warnings,
];
}
}
@@ -1161,11 +1158,11 @@ public static function get_responses_returns() {
'criterionid' => new external_value(PARAM_INT, 'The item ID for the response.'),
'value' => new external_value(PARAM_INT, 'The the value for the response.', VALUE_OPTIONAL, null),
'studentcomment' => new external_value(PARAM_RAW, 'The response public comment.'),
- 'privatecomment' => new external_value(PARAM_RAW, 'The response private comment.')
+ 'privatecomment' => new external_value(PARAM_RAW, 'The response private comment.'),
]
)
),
- 'warnings' => new external_warnings()
+ 'warnings' => new external_warnings(),
]
);
}
diff --git a/classes/forms/language_delete_form.php b/classes/forms/language_delete_form.php
index a80a8d0..c78f800 100644
--- a/classes/forms/language_delete_form.php
+++ b/classes/forms/language_delete_form.php
@@ -60,6 +60,6 @@ public function definition() {
* @return array
*/
public function validation($data, $files) {
- return array();
+ return [];
}
}
diff --git a/classes/forms/language_edit_form.php b/classes/forms/language_edit_form.php
index dbb1178..ccb21ea 100644
--- a/classes/forms/language_edit_form.php
+++ b/classes/forms/language_edit_form.php
@@ -58,6 +58,6 @@ public function definition() {
* @return array
*/
public function validation($data, $files) {
- return array();
+ return [];
}
}
diff --git a/classes/forms/template_category_delete_form.php b/classes/forms/template_category_delete_form.php
index 7e512da..520e554 100644
--- a/classes/forms/template_category_delete_form.php
+++ b/classes/forms/template_category_delete_form.php
@@ -61,6 +61,6 @@ public function definition() {
* @return array
*/
public function validation($data, $files) {
- return array();
+ return [];
}
}
diff --git a/classes/forms/template_category_edit_form.php b/classes/forms/template_category_edit_form.php
index c6cc065..47f0181 100644
--- a/classes/forms/template_category_edit_form.php
+++ b/classes/forms/template_category_edit_form.php
@@ -61,7 +61,7 @@ public function definition() {
$headers[] =& $mform->createElement('hidden', 'language_id', $l->get_id());
$categoryheader = get_string('categoryheader', 'verbalfeedback') . ' - ' . $l->get_language();
$headers[] =& $mform->createElement('text', 'string', $categoryheader);
- $mform->addGroup($headers, 'headers[' . $l->get_id() .']', $categoryheader, array(''), true);
+ $mform->addGroup($headers, 'headers[' . $l->get_id() .']', $categoryheader, [''], true);
$mform->setType('headers[' . $l->get_id() .'][id]', PARAM_INT);
$mform->setType('headers[' . $l->get_id() .'][language_id]', PARAM_INT);
@@ -73,7 +73,7 @@ public function definition() {
'Second textbox: weight within this category.');
$criteria = $templatecriteriarepo->get_all();
foreach ($criteria as $criterion) {
- $criteriongroup = array();
+ $criteriongroup = [];
$currentlanguage = current_language();
$localizedtext = '';
@@ -100,7 +100,7 @@ public function definition() {
$mform->setType($elementname . '[weight]', PARAM_FLOAT);
$mform->disabledIf($elementname . '[position]', $elementname . '[selected]', 'notchecked');
$mform->disabledIf($elementname . '[weight]', $elementname . '[selected]', 'notchecked');
- $mform->addGroup($criteriongroup, $elementname, $localizedtext, array(''), true);
+ $mform->addGroup($criteriongroup, $elementname, $localizedtext, [''], true);
}
$this->add_action_buttons($cancel = true);
@@ -114,6 +114,6 @@ public function definition() {
* @return array
*/
public function validation($data, $files) {
- return array();
+ return [];
}
}
diff --git a/classes/forms/template_criterion_delete_form.php b/classes/forms/template_criterion_delete_form.php
index 76dd123..ff00cde 100644
--- a/classes/forms/template_criterion_delete_form.php
+++ b/classes/forms/template_criterion_delete_form.php
@@ -61,7 +61,7 @@ public function definition() {
$localizedstring[] =& $mform->createElement('textarea', 'string', $textfieldname, $style);
$groupname = 'localized_strings[' . $l->get_id() .']';
- $mform->addGroup($localizedstring, $groupname, $textfieldname, array(''), true, 'disabled="disabled"');
+ $mform->addGroup($localizedstring, $groupname, $textfieldname, [''], true, 'disabled="disabled"');
$mform->setType('localized_strings[' . $l->get_id() .'][id]', PARAM_INT);
$mform->setType('localized_strings[' . $l->get_id() .'][language_id]', PARAM_INT);
@@ -79,6 +79,6 @@ public function definition() {
* @return array
*/
public function validation($data, $files) {
- return array();
+ return [];
}
}
diff --git a/classes/forms/template_criterion_edit_form.php b/classes/forms/template_criterion_edit_form.php
index d8d941f..197e6e1 100644
--- a/classes/forms/template_criterion_edit_form.php
+++ b/classes/forms/template_criterion_edit_form.php
@@ -79,9 +79,9 @@ public function definition() {
$mform->addElement('header', 'ratingsheader', 'Edit ratings');
- $repeateloptions = array();
+ $repeateloptions = [];
- $repeatarray = array();
+ $repeatarray = [];
$repeatarray[] = $mform->createElement('html', '
');
$repeatarray[] = $mform->createElement('static', 'header', '' . get_string('subrating', 'verbalfeedback') .
' - {no}
', '');
@@ -178,6 +178,6 @@ public function definition() {
* @return array
*/
public function validation($data, $files) {
- return array();
+ return [];
}
}
diff --git a/classes/forms/template_delete_form.php b/classes/forms/template_delete_form.php
index 091959a..036f4bc 100644
--- a/classes/forms/template_delete_form.php
+++ b/classes/forms/template_delete_form.php
@@ -64,6 +64,6 @@ public function definition() {
* @return array
*/
public function validation($data, $files) {
- return array();
+ return [];
}
}
diff --git a/classes/forms/template_edit_form.php b/classes/forms/template_edit_form.php
index 0d822a3..b24628e 100644
--- a/classes/forms/template_edit_form.php
+++ b/classes/forms/template_edit_form.php
@@ -60,7 +60,7 @@ public function definition() {
$mform->addElement('static', 'text', null, $statictext);
$templatecategories = $templatecategoryrepo->get_all();
foreach ($templatecategories as $templatecategory) {
- $categoryformgroup = array();
+ $categoryformgroup = [];
$categoryformgroup[] = $mform->createElement('hidden', 'param_category_id', 0); // Parametrized category id.
$categoryformgroup[] = $mform->createElement('hidden', 'template_category_id', $templatecategory->get_id());
@@ -75,7 +75,7 @@ public function definition() {
$mform->setType($elementname . '[weight]', PARAM_FLOAT);
$mform->disabledIf($elementname . '[position]', $elementname . '[selected]', 'notchecked');
$mform->disabledIf($elementname . '[weight]', $elementname . '[selected]', 'notchecked');
- $mform->addGroup($categoryformgroup, $elementname, $templatecategory->get_unique_name(), array(''), true);
+ $mform->addGroup($categoryformgroup, $elementname, $templatecategory->get_unique_name(), [''], true);
}
$this->add_action_buttons($cancel = true);
@@ -89,6 +89,6 @@ public function definition() {
* @return array
*/
public function validation($data, $files) {
- return array();
+ return [];
}
}
diff --git a/classes/helper.php b/classes/helper.php
index 2c9aee4..a1eb20c 100644
--- a/classes/helper.php
+++ b/classes/helper.php
@@ -86,12 +86,13 @@ public static function get_question_type_text($type) {
*/
public static function prepare_items_view($categories) {
$currentlanguage = current_language();
- $viewmodel = array();
+ $viewmodel = [];
+ /** @var instance_category $category */
foreach ($categories as $category) {
$categoryviewmodel = new stdClass();
$categoryviewmodel->header = $category->get_header($currentlanguage)->get_string();
$categoryviewmodel->id = $category->get_id();
- $categoryviewmodel->criteria = array();
+ $categoryviewmodel->criteria = [];
$categoryviewmodel->position = $category->get_position();
$categoryviewmodel->weight = number_format($category->get_weight(), 2);
@@ -124,4 +125,22 @@ public static function prepare_items_view($categories) {
}
return $viewmodel;
}
+
+ /**
+ * Wrapper for the Yaml::parseFile() function.
+ * @param string $someYaml
+ * @param int|null $options
+ * @param int|null $debug
+ * @return array|\Dallgoot\Yaml\Types\YamlObject|\Dallgoot\Yaml\YamlObject|null
+ * @throws Exception
+ */
+ public static function parseYamlFile(string $someYaml, ?int $options = null, ?int $debug = null)
+ {
+ if (version_compare(PHP_VERSION, '8.1.14') >= 0) {
+ require_once implode(DIRECTORY_SEPARATOR, [dirname(__FILE__), 'vendor', '81x', 'autoload.php']);
+ return \Dallgoot\Yaml\Yaml::parseFile($someYaml, $options, $debug);
+ }
+ require_once implode(DIRECTORY_SEPARATOR, [dirname(__FILE__), 'vendor', '74x', 'autoload.php']);
+ return \Dallgoot\Yaml::parseFile($someYaml, $options, $debug);
+ }
}
diff --git a/classes/model/instance.php b/classes/model/instance.php
index 4338689..b30efe0 100644
--- a/classes/model/instance.php
+++ b/classes/model/instance.php
@@ -98,7 +98,7 @@ class instance {
/**
* @var array The categories.
*/
- public $categories = array();
+ public $categories = [];
/**
* Class constructor
@@ -291,7 +291,7 @@ public function set_introformat(int $introformat) {
*/
public function get_grade() : int {
global $DB;
- return $DB->get_field('verbalfeedback', 'grade', array('id' => $this->id));
+ return $DB->get_field('verbalfeedback', 'grade', ['id' => $this->id]);
}
/**
diff --git a/classes/model/instance_category.php b/classes/model/instance_category.php
index c1bdb0f..ee1ee6f 100644
--- a/classes/model/instance_category.php
+++ b/classes/model/instance_category.php
@@ -39,13 +39,13 @@ class instance_category {
/** @var int|null The parametrized template category id */
public $paramtemplatecategoryid;
/** @var array The localized headers */
- public $localizedheaders = array();
+ public $localizedheaders = [];
/** @var int The category position */
private $position;
/** @var float The category weight */
private $weight;
/** @var array The category instance criteria */
- public $instancecriteria = array();
+ public $instancecriteria = [];
/**
* The category instance class constructor
diff --git a/classes/model/instance_criterion.php b/classes/model/instance_criterion.php
index 650fb09..a9bd4e0 100644
--- a/classes/model/instance_criterion.php
+++ b/classes/model/instance_criterion.php
@@ -41,9 +41,9 @@ class instance_criterion {
/** @var float The criterion weight */
public $weight;
/** @var array|mixed The criterion descriptions */
- public $descriptions = array();
+ public $descriptions = [];
/** @var array|mixed The criterion subratings */
- public $subratings = array();
+ public $subratings = [];
/**
* The criterion instance class constructor
@@ -56,7 +56,7 @@ class instance_criterion {
* @param array $subratings The criterion subratings
*/
public function __construct(int $id = 0, ?int $parametrizedtemplatecriterionid = null, int $position = 0,
- float $weight = 0.0, $descriptions = array(), $subratings = array()) {
+ float $weight = 0.0, $descriptions = [], $subratings = []) {
$this->id = $id;
$this->parametrizedtemplatecriterionid = $parametrizedtemplatecriterionid;
$this->position = $position;
@@ -93,7 +93,7 @@ public static function from_template(parametrized_template_criterion $parametriz
'verynegatives',
'negatives',
'positives',
- 'verypositives'
+ 'verypositives',
];
foreach ($templatecriterion->get_subratings() as $templatesubrating) {
diff --git a/classes/model/report.php b/classes/model/report.php
index 35b5c32..a31ba18 100644
--- a/classes/model/report.php
+++ b/classes/model/report.php
@@ -34,9 +34,9 @@ class report {
/** @var int The recipient of the feedback responses. */
private $touserid;
/** @var array The ids of the rating users */
- private $fromuserids = array();
+ private $fromuserids = [];
/** @var array The categories this report has */
- private $reportcategories = array();
+ private $reportcategories = [];
/** @var null The result */
private $result = null;
@@ -145,7 +145,7 @@ public function get_result_part() : float {
*/
public function get_max_points() : int {
global $DB;
- return $DB->get_field('verbalfeedback', 'grade', array('id' => $this->instanceid));
+ return $DB->get_field('verbalfeedback', 'grade', ['id' => $this->instanceid]);
}
/**
diff --git a/classes/model/report_category.php b/classes/model/report_category.php
index 8619dd0..7768948 100644
--- a/classes/model/report_category.php
+++ b/classes/model/report_category.php
@@ -160,7 +160,7 @@ private function update_avg() {
foreach ($this->reportcriteria as $criterion) {
$values[] = $criterion->get_avg();
}
- $values = array_filter($values, 'strlen');
+ $values = array_filter($values, fn($n) => $n !== null && $n !== false && $n !== '');
if (count($values) == 0) {
$this->avg = null;
} else {
diff --git a/classes/model/report_criterion.php b/classes/model/report_criterion.php
index dedd6ed..98c33b4 100644
--- a/classes/model/report_criterion.php
+++ b/classes/model/report_criterion.php
@@ -32,7 +32,7 @@ class report_criterion {
/** @var instance_criterion The instance criterion */
private $instancecriterion;
/** @var array The criterion responses */
- private $responses = array();
+ private $responses = [];
/** @var float The average of the response values */
private $avg;
@@ -148,11 +148,11 @@ public function get_student_comments() : array {
* Updates the average value
*/
private function update_avg() {
- $values = array();
+ $values = [];
foreach ($this->responses as $response) {
$values[] = $response->get_value();
}
- $values = array_filter($values, 'strlen');
+ $values = array_filter($values, fn($n) => $n !== null && $n !== false && $n !== '');
if (count($values) == 0) {
$this->avg = null;
} else {
diff --git a/classes/model/submission.php b/classes/model/submission.php
index 78012ed..c3c708b 100644
--- a/classes/model/submission.php
+++ b/classes/model/submission.php
@@ -40,7 +40,7 @@ class submission {
/** @var string The submission remarks */
public $remarks;
/** @var array The submission responses */
- public $responses = array();
+ public $responses = [];
/**
* The submission class constructor
diff --git a/classes/model/subrating.php b/classes/model/subrating.php
index a2bc732..1fe5928 100644
--- a/classes/model/subrating.php
+++ b/classes/model/subrating.php
@@ -33,17 +33,17 @@ class subrating {
/** @var int The id */
public $id = 0;
/** @var array The titles */
- public $titles = array();
+ public $titles = [];
/** @var array The descriptions */
- public $descriptions = array();
+ public $descriptions = [];
/** @var array The very negative subratings */
- public $verynegatives = array();
+ public $verynegatives = [];
/** @var array The negative subratings */
- public $negatives = array();
+ public $negatives = [];
/** @var array The positive subratings */
- public $positives = array();
+ public $positives = [];
/** @var array The very positive subratings */
- public $verypositives = array();
+ public $verypositives = [];
/**
* The subrating class constructor
diff --git a/classes/model/template/template.php b/classes/model/template/template.php
index 8df735a..faede6b 100644
--- a/classes/model/template/template.php
+++ b/classes/model/template/template.php
@@ -42,7 +42,7 @@ class template {
/**
* @var array
*/
- public $templatecategories = array();
+ public $templatecategories = [];
/**
* The template class constructor
diff --git a/classes/model/template/template_category.php b/classes/model/template/template_category.php
index a27c173..c116978 100644
--- a/classes/model/template/template_category.php
+++ b/classes/model/template/template_category.php
@@ -36,7 +36,7 @@ class template_category {
/** @var array The template category headers */
public $headers = [];
/** @var array The template category criteria */
- public $templatecriteria = array();
+ public $templatecriteria = [];
/**
* The template category model constructor
diff --git a/classes/model/template/template_criterion.php b/classes/model/template/template_criterion.php
index 44728b0..8d3f575 100644
--- a/classes/model/template/template_criterion.php
+++ b/classes/model/template/template_criterion.php
@@ -33,9 +33,9 @@ class template_criterion {
/** @var int The template criterion id */
public $id = 0;
/** @var array|mixed The template criterion descriptions */
- public $descriptions = array();
+ public $descriptions = [];
/** @var array|mixed The template criterion subratings */
- public $subratings = array();
+ public $subratings = [];
/**
* The template criterion model constructor
@@ -44,7 +44,7 @@ class template_criterion {
* @param array $descriptions
* @param array $subratings
*/
- public function __construct(int $id = 0, $descriptions = array(), $subratings = array()) {
+ public function __construct(int $id = 0, $descriptions = [], $subratings = []) {
$this->id = $id;
$this->descriptions = $descriptions;
$this->subratings = $subratings;
diff --git a/classes/output/list_verbalfeedback_items.php b/classes/output/list_verbalfeedback_items.php
index 0dc8a1a..b4a0551 100644
--- a/classes/output/list_verbalfeedback_items.php
+++ b/classes/output/list_verbalfeedback_items.php
@@ -102,7 +102,7 @@ public function export_for_template(renderer_base $output) {
global $CFG;
$data = new stdClass();
- $data->categories = array();
+ $data->categories = [];
$data->verbalfeedbackid = $this->verbalfeedbackid;
$data->viewurl = $this->viewurl;
$data->makeavailableurl = $this->makeavailableurl;
diff --git a/classes/output/model/report_category_view_model.php b/classes/output/model/report_category_view_model.php
index c466519..cad955a 100644
--- a/classes/output/model/report_category_view_model.php
+++ b/classes/output/model/report_category_view_model.php
@@ -36,7 +36,7 @@ class report_category_view_model {
/** @var \lang_string|string The percentage */
public $percentage;
/** @var array The criteria */
- public $criteria = array();
+ public $criteria = [];
/**
* The report category view model class constructor
diff --git a/classes/output/model/report_criterion_view_model.php b/classes/output/model/report_criterion_view_model.php
index 57b6294..688a971 100644
--- a/classes/output/model/report_criterion_view_model.php
+++ b/classes/output/model/report_criterion_view_model.php
@@ -64,9 +64,9 @@ public function __construct(report_criterion $criterion) {
if ($addfirstelement) {
// This is a nasty hack to allow mustache to decide whether to display the "comments" label.
$addfirstelement = false;
- $this->comments = array();
+ $this->comments = [];
$this->comments[0] = new \stdClass();
- $this->comments[0]->texts = array();
+ $this->comments[0]->texts = [];
}
$this->comments[0]->texts[] = $comment;
}
diff --git a/classes/output/model/report_view_model.php b/classes/output/model/report_view_model.php
index 2e61d8a..0a1a0d6 100644
--- a/classes/output/model/report_view_model.php
+++ b/classes/output/model/report_view_model.php
@@ -35,7 +35,7 @@ class report_view_model {
/** @var string The radar code */
public $radar;
/** @var array The categories */
- public $categories = array();
+ public $categories = [];
/**
* The report view model class constructor
diff --git a/classes/privacy/provider.php b/classes/privacy/provider.php
index 8e46b42..0c2e827 100644
--- a/classes/privacy/provider.php
+++ b/classes/privacy/provider.php
@@ -192,7 +192,7 @@ protected static function export_submission_data($contextids, $user, $respondent
$options = ['context' => $context];
if (!isset($submissionsdata[$submission->cmid])) {
$submissionsdata[$submission->cmid] = [
- 'name' => $submission->verbalfeedbackname
+ 'name' => $submission->verbalfeedbackname,
];
}
if ($respondent) {
@@ -212,7 +212,7 @@ protected static function export_submission_data($contextids, $user, $respondent
$context = context_module::instance($cmid);
$subcontext = [
$parent,
- get_string('submissions', 'mod_verbalfeedback')
+ get_string('submissions', 'mod_verbalfeedback'),
];
writer::with_context($context)->export_data($subcontext, (object)$data);
}
@@ -280,7 +280,7 @@ protected static function export_responses_data($contextids, $user, $respondent
$options = ['context' => $context];
if (!isset($responsesdata[$response->cmid])) {
$responsesdata[$response->cmid] = [
- 'name' => $response->verbalfeedbackname
+ 'name' => $response->verbalfeedbackname,
];
}
$question = format_string($response->question, true, $options);
@@ -314,7 +314,7 @@ protected static function export_responses_data($contextids, $user, $respondent
$context = context_module::instance($cmid);
$subcontext = [
$parent,
- get_string('responses', 'mod_verbalfeedback')
+ get_string('responses', 'mod_verbalfeedback'),
];
writer::with_context($context)->export_data($subcontext, (object)$data);
}
diff --git a/classes/repository/instance_repository.php b/classes/repository/instance_repository.php
index 3374a36..56d4268 100644
--- a/classes/repository/instance_repository.php
+++ b/classes/repository/instance_repository.php
@@ -112,35 +112,40 @@ public static function get_by_id(int $id) : instance {
}
// Load subrating descriptions.
- $dbosubratingdescriptions = self::get_strings(localized_string_type::INSTANCE_SUBRATING_DESCRIPTION, $subrating->get_id());
+ $dbosubratingdescriptions = self::get_strings(localized_string_type::INSTANCE_SUBRATING_DESCRIPTION,
+ $subrating->get_id());
foreach ($dbosubratingdescriptions as $dbosubratingdescription) {
$subratingdescription = db_localized_string::to_localized_string($dbosubratingdescription);
$subrating->add_description($subratingdescription);
}
// Load subrating very negative texts.
- $dboverynegatives = self::get_strings(localized_string_type::INSTANCE_SUBRATING_VERY_NEGATIVE, $subrating->get_id());
+ $dboverynegatives = self::get_strings(localized_string_type::INSTANCE_SUBRATING_VERY_NEGATIVE,
+ $subrating->get_id());
foreach ($dboverynegatives as $dboverynegative) {
$verynegative = db_localized_string::to_localized_string($dboverynegative);
$subrating->add_verynegative($verynegative);
}
// Load subrating negative texts.
- $dbonegatives = self::get_strings(localized_string_type::INSTANCE_SUBRATING_NEGATIVE, $subrating->get_id());
+ $dbonegatives = self::get_strings(localized_string_type::INSTANCE_SUBRATING_NEGATIVE,
+ $subrating->get_id());
foreach ($dbonegatives as $dbonegative) {
$negative = db_localized_string::to_localized_string($dbonegative);
$subrating->add_negative($negative);
}
// Load subrating positive texts.
- $dbopositives = self::get_strings(localized_string_type::INSTANCE_SUBRATING_POSITIVE, $subrating->get_id());
+ $dbopositives = self::get_strings(localized_string_type::INSTANCE_SUBRATING_POSITIVE,
+ $subrating->get_id());
foreach ($dbopositives as $dbopositive) {
$positive = db_localized_string::to_localized_string($dbopositive);
$subrating->add_positive($positive);
}
// Load subrating very positive texts.
- $dboverypositives = self::get_strings(localized_string_type::INSTANCE_SUBRATING_VERY_POSITIVE, $subrating->get_id());
+ $dboverypositives = self::get_strings(localized_string_type::INSTANCE_SUBRATING_VERY_POSITIVE,
+ $subrating->get_id());
foreach ($dboverypositives as $dboverypositive) {
$verypositive = db_localized_string::to_localized_string($dboverypositive);
$subrating->add_verypositive($verypositive);
@@ -166,14 +171,14 @@ private static function get_strings(string $type, int $subratingid, bool $throwo
$sortedstrings = [];
$rs = $DB->get_recordset(tables::LOCALIZED_STRING_TABLE);
foreach ($rs as $dboheader) {
- $dbo_obj = new db_localized_string;
- $dbo_obj->id = $dboheader->id;
- $dbo_obj->languageid = $dboheader->languageid;
- $dbo_obj->string = $dboheader->string;
- $dbo_obj->type = $dboheader->type;
- $dbo_obj->foreignkey = $dboheader->foreignkey;
-
- $sortedstrings[$dbo_obj->type][$dbo_obj->foreignkey][$dbo_obj->languageid] = $dbo_obj;
+ $dbobj = new db_localized_string;
+ $dbobj->id = $dboheader->id;
+ $dbobj->languageid = $dboheader->languageid;
+ $dbobj->string = $dboheader->string;
+ $dbobj->type = $dboheader->type;
+ $dbobj->foreignkey = $dboheader->foreignkey;
+
+ $sortedstrings[$dbobj->type][$dbobj->foreignkey][$dbobj->languageid] = $dbobj;
}
$rs->close();
}
@@ -209,7 +214,7 @@ private static function get_criteria_by_category_for_instance_id(int $id): array
$sql = "SELECT crit.*
FROM {{$crittab}} crit
JOIN {{$cattab}} cat
- ON crit.categoryid = cat.id
+ ON crit.categoryid = cat.id
WHERE cat.instanceid = ?";
$bycat = [];
@@ -240,7 +245,7 @@ private static function get_subratings_by_criterion_for_instance(int $id): array
JOIN {{$crittab}} crit
ON srat.criterionid = crit.id
JOIN {{$cattab}} mvic
- ON crit.categoryid = mvic.id
+ ON crit.categoryid = mvic.id
WHERE mvic.instanceid = ?";
$rs = $DB->get_recordset_sql($sql, [$id]);
$bycrit = [];
@@ -267,7 +272,7 @@ public function save(instance $instance) {
$id = $DB->insert_record(tables::INSTANCE_TABLE, $dboinstance);
$instance->set_id($id);
// Set the grade.
- $DB->set_field('verbalfeedback', 'grade', $instance->grade, array('id' => $id));
+ $DB->set_field('verbalfeedback', 'grade', $instance->grade, ['id' => $id]);
} else {
$DB->update_record(tables::INSTANCE_TABLE, $dboinstance);
}
diff --git a/classes/repository/language_repository.php b/classes/repository/language_repository.php
index f261c5b..284a641 100644
--- a/classes/repository/language_repository.php
+++ b/classes/repository/language_repository.php
@@ -68,7 +68,7 @@ public function delete_by_id(int $id) : bool {
*/
public function get_all() : array {
global $DB;
- $languages = array();
+ $languages = [];
$dbo = $DB->get_records(tables::LANGUAGE_TABLE);
foreach ($dbo as $o) {
diff --git a/classes/repository/model/db_parametrized_criterion.php b/classes/repository/model/db_parametrized_criterion.php
index 12a721c..afce388 100644
--- a/classes/repository/model/db_parametrized_criterion.php
+++ b/classes/repository/model/db_parametrized_criterion.php
@@ -29,19 +29,20 @@
* The database parametrized criterion class
*/
class db_parametrized_criterion {
- /**
- * @var
- */
+
+ /** @var int Primary key. */
+ public $id;
+
+ /** @var int Foreign key category id. */
public $categoryid;
- /** @var int The criterion id */
+
+ /** @var int The criterion id. */
public $criterionid;
- /**
- * @var
- */
+
+ /** @var int The position of the criterion. */
public $position;
- /**
- * @var
- */
+
+ /** @var float The weight of the criterion */
public $weight;
/**
diff --git a/classes/repository/submission_repository.php b/classes/repository/submission_repository.php
index 8333253..ee7b6d8 100644
--- a/classes/repository/submission_repository.php
+++ b/classes/repository/submission_repository.php
@@ -112,7 +112,7 @@ public function get_by_id(int $id) : submission {
public function get_by_instance_and_fromuser_and_touser(int $instanceid, int $fromuserid, int $touserid) : submission {
global $DB;
$dbo = $DB->get_record(tables::SUBMISSION_TABLE, ["instanceid" => $instanceid, "fromuserid" => $fromuserid,
- "touserid" => $touserid]);
+ "touserid" => $touserid, ], );
$submission = db_submission::to_submission($dbo);
$dboresponses = $DB->get_records(tables::RESPONSE_TABLE, ["submissionid" => $submission->get_id()]);
diff --git a/classes/repository/template_category_repository.php b/classes/repository/template_category_repository.php
index df3bd4b..6344e43 100644
--- a/classes/repository/template_category_repository.php
+++ b/classes/repository/template_category_repository.php
@@ -65,7 +65,7 @@ public function get_all() : array {
foreach ($templatecategories as $catid => $templatecategory) {
$headers = $headersbycatids[$catid];
$templatecategory->set_headers($headers);
- $parametrizedcriteria = $parametrizedcriteriabycatid[$catid];
+ $parametrizedcriteria = $parametrizedcriteriabycatid[$catid] ?? [];
$templatecategory->set_template_criteria($parametrizedcriteria);
$results[$catid] = $templatecategory;
@@ -141,7 +141,7 @@ public function delete_by_id(int $id) : bool {
try {
$transaction = $DB->start_delegated_transaction();
$DB->delete_records('verbalfeedback_local_string', ['foreignkey' => $id,
- 'type' => localized_string_type::TEMPLATE_CATEGORY_HEADER]);
+ 'type' => localized_string_type::TEMPLATE_CATEGORY_HEADER, ]);
$DB->delete_records('verbalfeedback_t_param_crit', ['categoryid' => $id]);
$DB->delete_records('verbalfeedback_t_category', ['id' => $id]);
@@ -162,8 +162,8 @@ public function delete_by_id(int $id) : bool {
private function get_headers($foreignkey) : array {
global $DB;
- $dboheaders = $DB->get_records('verbalfeedback_local_string', ['foreignkey' => $foreignkey,
- 'type' => localized_string_type::TEMPLATE_CATEGORY_HEADER]);
+ $dboheaders = $DB->get_records('verbalfeedback_local_string',
+ ['foreignkey' => $foreignkey, 'type' => localized_string_type::TEMPLATE_CATEGORY_HEADER]);
$headers = [];
foreach ($dboheaders as $dboheader) {
$headers[] = db_localized_string::to_localized_string($dboheader);
@@ -184,7 +184,7 @@ private function get_headers_by_category_ids() : array {
$headers = [];
$rs = $DB->get_recordset('verbalfeedback_local_string', ['type' => localized_string_type::TEMPLATE_CATEGORY_HEADER]);
foreach ($rs as $row) {
- if (!isset($dboheaders[$row->foreignkey])) {
+ if (!\array_key_exists($row->foreignkey, $headers)) {
$headers[$row->foreignkey] = [];
}
$headers[$row->foreignkey][] = new localized_string($row->languageid, $row->id, $row->string);
@@ -222,7 +222,7 @@ private function get_parameterized_criteria_by_categoryid() : array {
$critbycatid = [];
$rs = $DB->get_recordset('verbalfeedback_t_param_crit');
foreach ($rs as $row) {
- if (!isset($critbycatid[$row->categoryid])) {
+ if (!\array_key_exists($row->categoryid, $critbycatid)) {
$critbycatid[$row->categoryid] = [];
}
$critbycatid[$row->categoryid][] = db_parametrized_criterion::to_parametrized_criterion($row);
diff --git a/classes/repository/template_criterion_repository.php b/classes/repository/template_criterion_repository.php
index 8411449..99f1f45 100644
--- a/classes/repository/template_criterion_repository.php
+++ b/classes/repository/template_criterion_repository.php
@@ -243,23 +243,23 @@ public function delete(int $id) {
$dbosubratings = $DB->get_records(tables::TEMPLATE_SUBRATINGS_TABLE, ['criterionid' => $id]);
foreach ($dbosubratings as $dbosubrating) {
$DB->delete_records(tables::LOCALIZED_STRING_TABLE, ['foreignkey' => $dbosubrating->id,
- 'type' => localized_string_type::TEMPLATE_SUBRATING_TITLE]);
+ 'type' => localized_string_type::TEMPLATE_SUBRATING_TITLE, ], );
$DB->delete_records(tables::LOCALIZED_STRING_TABLE, ['foreignkey' => $dbosubrating->id,
- 'type' => localized_string_type::TEMPLATE_SUBRATING_DESCRIPTION]);
+ 'type' => localized_string_type::TEMPLATE_SUBRATING_DESCRIPTION, ], );
$DB->delete_records(tables::LOCALIZED_STRING_TABLE, ['foreignkey' => $dbosubrating->id,
- 'type' => localized_string_type::TEMPLATE_SUBRATING_VERY_NEGATIVE]);
+ 'type' => localized_string_type::TEMPLATE_SUBRATING_VERY_NEGATIVE, ], );
$DB->delete_records(tables::LOCALIZED_STRING_TABLE, ['foreignkey' => $dbosubrating->id,
- 'type' => localized_string_type::TEMPLATE_SUBRATING_NEGATIVE]);
+ 'type' => localized_string_type::TEMPLATE_SUBRATING_NEGATIVE, ], );
$DB->delete_records(tables::LOCALIZED_STRING_TABLE, ['foreignkey' => $dbosubrating->id,
- 'type' => localized_string_type::TEMPLATE_SUBRATING_POSITIVE]);
+ 'type' => localized_string_type::TEMPLATE_SUBRATING_POSITIVE, ], );
$DB->delete_records(tables::LOCALIZED_STRING_TABLE, ['foreignkey' => $dbosubrating->id,
- 'type' => localized_string_type::TEMPLATE_SUBRATING_VERY_POSITIVE]);
+ 'type' => localized_string_type::TEMPLATE_SUBRATING_VERY_POSITIVE, ], );
}
$DB->delete_records(tables::TEMPLATE_SUBRATINGS_TABLE, ['criterionid' => $id]);
// Delete localized strings.
$DB->delete_records(tables::LOCALIZED_STRING_TABLE, ['foreignkey' => $id,
- 'type' => localized_string_type::TEMPLATE_CRITERION]);
+ 'type' => localized_string_type::TEMPLATE_CRITERION, ], );
// Delete criterion.
$DB->delete_records(tables::TEMPLATE_CRITERION_TABLE, ['id' => $id]);
diff --git a/classes/repository/template_repository.php b/classes/repository/template_repository.php
index c63e8a9..ee9bd89 100644
--- a/classes/repository/template_repository.php
+++ b/classes/repository/template_repository.php
@@ -48,7 +48,7 @@ public function get_all() : array {
$dboparametrizedcategories =
$DB->get_records(tables::PARAMETRIZED_TEMPLATE_CATEGORY_TABLE, ['templateid' => $template->get_id()]);
- $parametrizedcategories = array();
+ $parametrizedcategories = [];
foreach ($dboparametrizedcategories as $o) {
$parametrizedcategories[] = db_parametrized_category::to_parametrized_category($o);
}
@@ -72,7 +72,7 @@ public function get_by_id(int $id) : template {
$dboparametrizedcategories =
$DB->get_records(tables::PARAMETRIZED_TEMPLATE_CATEGORY_TABLE, ['templateid' => $template->get_id()]);
- $parametrizedcategories = array();
+ $parametrizedcategories = [];
foreach ($dboparametrizedcategories as $o) {
$parametrizedcategories[] = db_parametrized_category::to_parametrized_category($o);
}
diff --git a/classes/utils/calendar_utils.php b/classes/utils/calendar_utils.php
index c9c2e78..263c022 100644
--- a/classes/utils/calendar_utils.php
+++ b/classes/utils/calendar_utils.php
@@ -116,7 +116,7 @@ protected static function set_event($id, $eventname, $description, $eventtype, $
// Check if event exists.
$event->id = $DB->get_field('event', 'id', ['modulename' => 'verbalfeedback', 'instance' => $id,
- 'eventtype' => $eventtype]);
+ 'eventtype' => $eventtype, ]);
if ($event->id) {
$calendarevent = calendar_event::load($event->id);
if ($timestamp) {
diff --git a/classes/utils/graph_utils.php b/classes/utils/graph_utils.php
index a9ffa01..54db12d 100644
--- a/classes/utils/graph_utils.php
+++ b/classes/utils/graph_utils.php
@@ -27,10 +27,8 @@
defined('MOODLE_INTERNAL') || die();
-use Goat1000\SVGGraph\SVGGraph;
use mod_verbalfeedback\model\report;
-require_once('./classes/vendor/autoload.php');
/**
* Class for creating graphs. For example, to display on the report.
@@ -92,11 +90,29 @@ public static function create_radar_graph(report $report) : string {
}
}
- $graph = new SVGGraph($width, $height, $settings);
+ $graph = self::getSvgGraph($width, $height, $settings);
$graph->values($values);
return $graph->fetch($type, true);
}
+ /**
+ * Wrapper for creating a new SVGGraph instance.
+ * @param $w
+ * @param $h
+ * @param $settings
+ * @param $subgraph
+ * @return \Goat1000\SVGGraph\SVGGraph
+ */
+ public static function getSvgGraph($w, $h, $settings = null, $subgraph = false) {
+ $dir = (version_compare(PHP_VERSION, '8.1.14') >= 0)
+ ? '81x'
+ : '74x';
+
+ require_once implode(DIRECTORY_SEPARATOR,
+ [dirname(__FILE__), '..', 'vendor', $dir, 'autoload.php']);
+
+ return new \Goat1000\SVGGraph\SVGGraph($w, $h, $settings, $subgraph);
+ }
}
diff --git a/classes/utils/instance_utils.php b/classes/utils/instance_utils.php
index 5ba1edf..ad6f2cd 100644
--- a/classes/utils/instance_utils.php
+++ b/classes/utils/instance_utils.php
@@ -150,7 +150,7 @@ protected static function set_event($id, $eventname, $description, $eventtype, $
// Check if event exists.
$event->id = $DB->get_field('event', 'id', ['modulename' => 'verbalfeedback', 'instance' => $id,
- 'eventtype' => $eventtype]);
+ 'eventtype' => $eventtype, ]);
if ($event->id) {
$calendarevent = calendar_event::load($event->id);
if ($timestamp) {
@@ -194,7 +194,7 @@ public static function verbalfeedback_set_grade(float $newgrade, object $verbalf
$transaction = $DB->start_delegated_transaction();
// Update the verbal feedback table.
- $DB->set_field('verbalfeedback', 'grade', $newgrade, array('id' => $verbalfeedback->id));
+ $DB->set_field('verbalfeedback', 'grade', $newgrade, ['id' => $verbalfeedback->id]);
$transaction->allow_commit();
return true;
diff --git a/classes/utils/user_utils.php b/classes/utils/user_utils.php
index 7b59616..8706ae2 100644
--- a/classes/utils/user_utils.php
+++ b/classes/utils/user_utils.php
@@ -144,7 +144,7 @@ public static function can_participate(instance $instance, $userid) {
return false;
}
// User can't participate if not enrolled in the course.
- if ((!self::is_enrolled($context, $userid)) AND (!has_capability('moodle/site:config', $context))) {
+ if ((!self::is_enrolled($context, $userid)) && (!has_capability('moodle/site:config', $context))) {
return get_string('errornotenrolled', 'mod_verbalfeedback');
}
if (has_capability('mod/verbalfeedback:can_participate', $context)) {
diff --git a/classes/vendor/74x/autoload.php b/classes/vendor/74x/autoload.php
new file mode 100644
index 0000000..2a215e5
--- /dev/null
+++ b/classes/vendor/74x/autoload.php
@@ -0,0 +1,25 @@
+
* @author Jordi Boggiano
- * @see http://www.php-fig.org/psr/psr-0/
- * @see http://www.php-fig.org/psr/psr-4/
+ * @see https://www.php-fig.org/psr/psr-0/
+ * @see https://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
+ /** @var \Closure(string):void */
+ private static $includeFile;
+
+ /** @var string|null */
+ private $vendorDir;
+
// PSR-4
+ /**
+ * @var array>
+ */
private $prefixLengthsPsr4 = array();
+ /**
+ * @var array>
+ */
private $prefixDirsPsr4 = array();
+ /**
+ * @var list
+ */
private $fallbackDirsPsr4 = array();
// PSR-0
+ /**
+ * List of PSR-0 prefixes
+ *
+ * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
+ *
+ * @var array>>
+ */
private $prefixesPsr0 = array();
+ /**
+ * @var list
+ */
private $fallbackDirsPsr0 = array();
+ /** @var bool */
private $useIncludePath = false;
+
+ /**
+ * @var array
+ */
private $classMap = array();
+
+ /** @var bool */
private $classMapAuthoritative = false;
+
+ /**
+ * @var array
+ */
private $missingClasses = array();
+
+ /** @var string|null */
private $apcuPrefix;
+ /**
+ * @var array
+ */
+ private static $registeredLoaders = array();
+
+ /**
+ * @param string|null $vendorDir
+ */
+ public function __construct($vendorDir = null)
+ {
+ $this->vendorDir = $vendorDir;
+ self::initializeIncludeClosure();
+ }
+
+ /**
+ * @return array>
+ */
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
@@ -66,28 +121,42 @@ public function getPrefixes()
return array();
}
+ /**
+ * @return array>
+ */
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
+ /**
+ * @return list
+ */
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
+ /**
+ * @return list
+ */
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
+ /**
+ * @return array Array of classname => path
+ */
public function getClassMap()
{
return $this->classMap;
}
/**
- * @param array $classMap Class to filename map
+ * @param array $classMap Class to filename map
+ *
+ * @return void
*/
public function addClassMap(array $classMap)
{
@@ -102,22 +171,25 @@ public function addClassMap(array $classMap)
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
- * @param string $prefix The prefix
- * @param array|string $paths The PSR-0 root directories
- * @param bool $prepend Whether to prepend the directories
+ * @param string $prefix The prefix
+ * @param list|string $paths The PSR-0 root directories
+ * @param bool $prepend Whether to prepend the directories
+ *
+ * @return void
*/
public function add($prefix, $paths, $prepend = false)
{
+ $paths = (array) $paths;
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
- (array) $paths,
+ $paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
- (array) $paths
+ $paths
);
}
@@ -126,19 +198,19 @@ public function add($prefix, $paths, $prepend = false)
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
- $this->prefixesPsr0[$first][$prefix] = (array) $paths;
+ $this->prefixesPsr0[$first][$prefix] = $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
- (array) $paths,
+ $paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
- (array) $paths
+ $paths
);
}
}
@@ -147,25 +219,28 @@ public function add($prefix, $paths, $prepend = false)
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
- * @param string $prefix The prefix/namespace, with trailing '\\'
- * @param array|string $paths The PSR-4 base directories
- * @param bool $prepend Whether to prepend the directories
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param list|string $paths The PSR-4 base directories
+ * @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
+ *
+ * @return void
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
+ $paths = (array) $paths;
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
- (array) $paths,
+ $paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
- (array) $paths
+ $paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
@@ -175,18 +250,18 @@ public function addPsr4($prefix, $paths, $prepend = false)
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
- $this->prefixDirsPsr4[$prefix] = (array) $paths;
+ $this->prefixDirsPsr4[$prefix] = $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
- (array) $paths,
+ $paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
- (array) $paths
+ $paths
);
}
}
@@ -195,8 +270,10 @@ public function addPsr4($prefix, $paths, $prepend = false)
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
- * @param string $prefix The prefix
- * @param array|string $paths The PSR-0 base directories
+ * @param string $prefix The prefix
+ * @param list|string $paths The PSR-0 base directories
+ *
+ * @return void
*/
public function set($prefix, $paths)
{
@@ -211,10 +288,12 @@ public function set($prefix, $paths)
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
- * @param string $prefix The prefix/namespace, with trailing '\\'
- * @param array|string $paths The PSR-4 base directories
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param list|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
+ *
+ * @return void
*/
public function setPsr4($prefix, $paths)
{
@@ -234,6 +313,8 @@ public function setPsr4($prefix, $paths)
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
+ *
+ * @return void
*/
public function setUseIncludePath($useIncludePath)
{
@@ -256,6 +337,8 @@ public function getUseIncludePath()
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
+ *
+ * @return void
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
@@ -276,6 +359,8 @@ public function isClassMapAuthoritative()
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
+ *
+ * @return void
*/
public function setApcuPrefix($apcuPrefix)
{
@@ -296,33 +381,55 @@ public function getApcuPrefix()
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
+ *
+ * @return void
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
+
+ if (null === $this->vendorDir) {
+ return;
+ }
+
+ if ($prepend) {
+ self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
+ } else {
+ unset(self::$registeredLoaders[$this->vendorDir]);
+ self::$registeredLoaders[$this->vendorDir] = $this;
+ }
}
/**
* Unregisters this instance as an autoloader.
+ *
+ * @return void
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
+
+ if (null !== $this->vendorDir) {
+ unset(self::$registeredLoaders[$this->vendorDir]);
+ }
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
- * @return bool|null True if loaded, null otherwise
+ * @return true|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
- includeFile($file);
+ $includeFile = self::$includeFile;
+ $includeFile($file);
return true;
}
+
+ return null;
}
/**
@@ -367,6 +474,21 @@ public function findFile($class)
return $file;
}
+ /**
+ * Returns the currently registered loaders keyed by their corresponding vendor directories.
+ *
+ * @return array
+ */
+ public static function getRegisteredLoaders()
+ {
+ return self::$registeredLoaders;
+ }
+
+ /**
+ * @param string $class
+ * @param string $ext
+ * @return string|false
+ */
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
@@ -432,14 +554,26 @@ private function findFileWithExtension($class, $ext)
return false;
}
-}
-/**
- * Scope isolated include.
- *
- * Prevents access to $this/self from included files.
- */
-function includeFile($file)
-{
- include $file;
+ /**
+ * @return void
+ */
+ private static function initializeIncludeClosure()
+ {
+ if (self::$includeFile !== null) {
+ return;
+ }
+
+ /**
+ * Scope isolated include.
+ *
+ * Prevents access to $this/self from included files.
+ *
+ * @param string $file
+ * @return void
+ */
+ self::$includeFile = \Closure::bind(static function($file) {
+ include $file;
+ }, null, null);
+ }
}
diff --git a/classes/vendor/74x/composer/InstalledVersions.php b/classes/vendor/74x/composer/InstalledVersions.php
new file mode 100644
index 0000000..51e734a
--- /dev/null
+++ b/classes/vendor/74x/composer/InstalledVersions.php
@@ -0,0 +1,359 @@
+
+ * Jordi Boggiano
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer;
+
+use Composer\Autoload\ClassLoader;
+use Composer\Semver\VersionParser;
+
+/**
+ * This class is copied in every Composer installed project and available to all
+ *
+ * See also https://getcomposer.org/doc/07-runtime.md#installed-versions
+ *
+ * To require its presence, you can require `composer-runtime-api ^2.0`
+ *
+ * @final
+ */
+class InstalledVersions
+{
+ /**
+ * @var mixed[]|null
+ * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null
+ */
+ private static $installed;
+
+ /**
+ * @var bool|null
+ */
+ private static $canGetVendors;
+
+ /**
+ * @var array[]
+ * @psalm-var array}>
+ */
+ private static $installedByVendor = array();
+
+ /**
+ * Returns a list of all package names which are present, either by being installed, replaced or provided
+ *
+ * @return string[]
+ * @psalm-return list
+ */
+ public static function getInstalledPackages()
+ {
+ $packages = array();
+ foreach (self::getInstalled() as $installed) {
+ $packages[] = array_keys($installed['versions']);
+ }
+
+ if (1 === \count($packages)) {
+ return $packages[0];
+ }
+
+ return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
+ }
+
+ /**
+ * Returns a list of all package names with a specific type e.g. 'library'
+ *
+ * @param string $type
+ * @return string[]
+ * @psalm-return list
+ */
+ public static function getInstalledPackagesByType($type)
+ {
+ $packagesByType = array();
+
+ foreach (self::getInstalled() as $installed) {
+ foreach ($installed['versions'] as $name => $package) {
+ if (isset($package['type']) && $package['type'] === $type) {
+ $packagesByType[] = $name;
+ }
+ }
+ }
+
+ return $packagesByType;
+ }
+
+ /**
+ * Checks whether the given package is installed
+ *
+ * This also returns true if the package name is provided or replaced by another package
+ *
+ * @param string $packageName
+ * @param bool $includeDevRequirements
+ * @return bool
+ */
+ public static function isInstalled($packageName, $includeDevRequirements = true)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (isset($installed['versions'][$packageName])) {
+ return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks whether the given package satisfies a version constraint
+ *
+ * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
+ *
+ * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
+ *
+ * @param VersionParser $parser Install composer/semver to have access to this class and functionality
+ * @param string $packageName
+ * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
+ * @return bool
+ */
+ public static function satisfies(VersionParser $parser, $packageName, $constraint)
+ {
+ $constraint = $parser->parseConstraints((string) $constraint);
+ $provided = $parser->parseConstraints(self::getVersionRanges($packageName));
+
+ return $provided->matches($constraint);
+ }
+
+ /**
+ * Returns a version constraint representing all the range(s) which are installed for a given package
+ *
+ * It is easier to use this via isInstalled() with the $constraint argument if you need to check
+ * whether a given version of a package is installed, and not just whether it exists
+ *
+ * @param string $packageName
+ * @return string Version constraint usable with composer/semver
+ */
+ public static function getVersionRanges($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ $ranges = array();
+ if (isset($installed['versions'][$packageName]['pretty_version'])) {
+ $ranges[] = $installed['versions'][$packageName]['pretty_version'];
+ }
+ if (array_key_exists('aliases', $installed['versions'][$packageName])) {
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
+ }
+ if (array_key_exists('replaced', $installed['versions'][$packageName])) {
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
+ }
+ if (array_key_exists('provided', $installed['versions'][$packageName])) {
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
+ }
+
+ return implode(' || ', $ranges);
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
+ */
+ public static function getVersion($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ if (!isset($installed['versions'][$packageName]['version'])) {
+ return null;
+ }
+
+ return $installed['versions'][$packageName]['version'];
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
+ */
+ public static function getPrettyVersion($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ if (!isset($installed['versions'][$packageName]['pretty_version'])) {
+ return null;
+ }
+
+ return $installed['versions'][$packageName]['pretty_version'];
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
+ */
+ public static function getReference($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ if (!isset($installed['versions'][$packageName]['reference'])) {
+ return null;
+ }
+
+ return $installed['versions'][$packageName]['reference'];
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
+ */
+ public static function getInstallPath($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @return array
+ * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
+ */
+ public static function getRootPackage()
+ {
+ $installed = self::getInstalled();
+
+ return $installed[0]['root'];
+ }
+
+ /**
+ * Returns the raw installed.php data for custom implementations
+ *
+ * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
+ * @return array[]
+ * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}
+ */
+ public static function getRawData()
+ {
+ @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
+
+ if (null === self::$installed) {
+ // only require the installed.php file if this file is loaded from its dumped location,
+ // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
+ if (substr(__DIR__, -8, 1) !== 'C') {
+ self::$installed = include __DIR__ . '/installed.php';
+ } else {
+ self::$installed = array();
+ }
+ }
+
+ return self::$installed;
+ }
+
+ /**
+ * Returns the raw data of all installed.php which are currently loaded for custom implementations
+ *
+ * @return array[]
+ * @psalm-return list}>
+ */
+ public static function getAllRawData()
+ {
+ return self::getInstalled();
+ }
+
+ /**
+ * Lets you reload the static array from another file
+ *
+ * This is only useful for complex integrations in which a project needs to use
+ * this class but then also needs to execute another project's autoloader in process,
+ * and wants to ensure both projects have access to their version of installed.php.
+ *
+ * A typical case would be PHPUnit, where it would need to make sure it reads all
+ * the data it needs from this class, then call reload() with
+ * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
+ * the project in which it runs can then also use this class safely, without
+ * interference between PHPUnit's dependencies and the project's dependencies.
+ *
+ * @param array[] $data A vendor/composer/installed.php data set
+ * @return void
+ *
+ * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data
+ */
+ public static function reload($data)
+ {
+ self::$installed = $data;
+ self::$installedByVendor = array();
+ }
+
+ /**
+ * @return array[]
+ * @psalm-return list}>
+ */
+ private static function getInstalled()
+ {
+ if (null === self::$canGetVendors) {
+ self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
+ }
+
+ $installed = array();
+
+ if (self::$canGetVendors) {
+ foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
+ if (isset(self::$installedByVendor[$vendorDir])) {
+ $installed[] = self::$installedByVendor[$vendorDir];
+ } elseif (is_file($vendorDir.'/composer/installed.php')) {
+ /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */
+ $required = require $vendorDir.'/composer/installed.php';
+ $installed[] = self::$installedByVendor[$vendorDir] = $required;
+ if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
+ self::$installed = $installed[count($installed) - 1];
+ }
+ }
+ }
+ }
+
+ if (null === self::$installed) {
+ // only require the installed.php file if this file is loaded from its dumped location,
+ // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
+ if (substr(__DIR__, -8, 1) !== 'C') {
+ /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */
+ $required = require __DIR__ . '/installed.php';
+ self::$installed = $required;
+ } else {
+ self::$installed = array();
+ }
+ }
+
+ if (self::$installed !== array()) {
+ $installed[] = self::$installed;
+ }
+
+ return $installed;
+ }
+}
diff --git a/classes/vendor/composer/LICENSE b/classes/vendor/74x/composer/LICENSE
similarity index 100%
rename from classes/vendor/composer/LICENSE
rename to classes/vendor/74x/composer/LICENSE
diff --git a/classes/vendor/composer/autoload_classmap.php b/classes/vendor/74x/composer/autoload_classmap.php
similarity index 95%
rename from classes/vendor/composer/autoload_classmap.php
rename to classes/vendor/74x/composer/autoload_classmap.php
index c3a0d5a..da76738 100644
--- a/classes/vendor/composer/autoload_classmap.php
+++ b/classes/vendor/74x/composer/autoload_classmap.php
@@ -2,10 +2,11 @@
// autoload_classmap.php @generated by Composer
-$vendorDir = dirname(dirname(__FILE__));
-$baseDir = dirname(dirname($vendorDir));
+$vendorDir = dirname(__DIR__);
+$baseDir = dirname(dirname(dirname($vendorDir))).'/dependency_74x';
return array(
+ 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
'Dallgoot\\Yaml' => $vendorDir . '/dallgoot/yaml/sources/Yaml.php',
'Dallgoot\\Yaml\\Builder' => $vendorDir . '/dallgoot/yaml/sources/Builder.php',
'Dallgoot\\Yaml\\Compact' => $vendorDir . '/dallgoot/yaml/sources/types/Compact.php',
diff --git a/classes/vendor/74x/composer/autoload_namespaces.php b/classes/vendor/74x/composer/autoload_namespaces.php
new file mode 100644
index 0000000..0ff4450
--- /dev/null
+++ b/classes/vendor/74x/composer/autoload_namespaces.php
@@ -0,0 +1,9 @@
+ array($vendorDir . '/goat1000/svggraph'),
diff --git a/classes/vendor/74x/composer/autoload_real.php b/classes/vendor/74x/composer/autoload_real.php
new file mode 100644
index 0000000..ade2cbb
--- /dev/null
+++ b/classes/vendor/74x/composer/autoload_real.php
@@ -0,0 +1,38 @@
+register(true);
+
+ return $loader;
+ }
+}
diff --git a/classes/vendor/composer/autoload_static.php b/classes/vendor/74x/composer/autoload_static.php
similarity index 90%
rename from classes/vendor/composer/autoload_static.php
rename to classes/vendor/74x/composer/autoload_static.php
index 80bdb87..fefdada 100644
--- a/classes/vendor/composer/autoload_static.php
+++ b/classes/vendor/74x/composer/autoload_static.php
@@ -4,7 +4,7 @@
namespace Composer\Autoload;
-class ComposerStaticInit0b5f153730a4a4fe6e88cb8865fd9823
+class ComposerStaticInite7295e73671fedf6799e4b530315e6b4
{
public static $prefixLengthsPsr4 = array (
'G' =>
@@ -21,6 +21,7 @@ class ComposerStaticInit0b5f153730a4a4fe6e88cb8865fd9823
);
public static $classMap = array (
+ 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
'Dallgoot\\Yaml' => __DIR__ . '/..' . '/dallgoot/yaml/sources/Yaml.php',
'Dallgoot\\Yaml\\Builder' => __DIR__ . '/..' . '/dallgoot/yaml/sources/Builder.php',
'Dallgoot\\Yaml\\Compact' => __DIR__ . '/..' . '/dallgoot/yaml/sources/types/Compact.php',
@@ -65,9 +66,9 @@ class ComposerStaticInit0b5f153730a4a4fe6e88cb8865fd9823
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
- $loader->prefixLengthsPsr4 = ComposerStaticInit0b5f153730a4a4fe6e88cb8865fd9823::$prefixLengthsPsr4;
- $loader->prefixDirsPsr4 = ComposerStaticInit0b5f153730a4a4fe6e88cb8865fd9823::$prefixDirsPsr4;
- $loader->classMap = ComposerStaticInit0b5f153730a4a4fe6e88cb8865fd9823::$classMap;
+ $loader->prefixLengthsPsr4 = ComposerStaticInite7295e73671fedf6799e4b530315e6b4::$prefixLengthsPsr4;
+ $loader->prefixDirsPsr4 = ComposerStaticInite7295e73671fedf6799e4b530315e6b4::$prefixDirsPsr4;
+ $loader->classMap = ComposerStaticInite7295e73671fedf6799e4b530315e6b4::$classMap;
}, null, ClassLoader::class);
}
diff --git a/classes/vendor/74x/composer/installed.json b/classes/vendor/74x/composer/installed.json
new file mode 100644
index 0000000..c9e82f7
--- /dev/null
+++ b/classes/vendor/74x/composer/installed.json
@@ -0,0 +1,111 @@
+{
+ "packages": [
+ {
+ "name": "dallgoot/yaml",
+ "version": "v0.3.2",
+ "version_normalized": "0.3.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/dallgoot/yaml.git",
+ "reference": "505ce0f23b40767d3016ea16e1f101085f3a63c0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/dallgoot/yaml/zipball/505ce0f23b40767d3016ea16e1f101085f3a63c0",
+ "reference": "505ce0f23b40767d3016ea16e1f101085f3a63c0",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "ext-pcre": "*",
+ "ext-spl": "*",
+ "lib-pcre": "*",
+ "php": ">=7.1.10"
+ },
+ "require-dev": {
+ "composer/xdebug-handler": "^1.3",
+ "ext-reflection": "*",
+ "paulthebaud/phpunit-generator": "^2.1",
+ "phan/phan": "^1.3",
+ "phploc/phploc": "^4.0",
+ "phpmetrics/phpmetrics": "^2.4",
+ "phpunit/phpunit": "^7",
+ "roave/better-reflection": "dev-master#c87d856"
+ },
+ "time": "2020-01-08T19:03:03+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "classmap": [
+ "sources"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "authors": [
+ {
+ "name": "dallgoot",
+ "email": "stephane.rebai@gmail.com"
+ }
+ ],
+ "description": "Provides loader, dumper and an API for YAML content. Loader builds to equivalent data types in PHP 7.x",
+ "homepage": "https://github.com/dallgoot/yaml",
+ "keywords": [
+ "parser",
+ "yaml"
+ ],
+ "support": {
+ "issues": "https://github.com/dallgoot/yaml/issues",
+ "source": "https://github.com/dallgoot/yaml/tree/v0.3.2"
+ },
+ "install-path": "../dallgoot/yaml"
+ },
+ {
+ "name": "goat1000/svggraph",
+ "version": "3.20.0",
+ "version_normalized": "3.20.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/goat1000/SVGGraph.git",
+ "reference": "99576c9ad38763b8f10e6b03a9cff1ce32604869"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/goat1000/SVGGraph/zipball/99576c9ad38763b8f10e6b03a9cff1ce32604869",
+ "reference": "99576c9ad38763b8f10e6b03a9cff1ce32604869",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "lib-pcre": "*",
+ "php": ">=5.4.0"
+ },
+ "suggest": {
+ "ext-iconv": "For non-ASCII text measurement support"
+ },
+ "time": "2023-04-25T08:27:40+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Goat1000\\SVGGraph\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "LGPL-3.0-only"
+ ],
+ "description": "Generates SVG graphs",
+ "homepage": "http://www.goat1000.com/svggraph.php",
+ "support": {
+ "issues": "https://github.com/goat1000/SVGGraph/issues",
+ "source": "https://github.com/goat1000/SVGGraph/tree/3.20.0"
+ },
+ "install-path": "../goat1000/svggraph"
+ }
+ ],
+ "dev": true,
+ "dev-package-names": []
+}
diff --git a/classes/vendor/74x/composer/installed.php b/classes/vendor/74x/composer/installed.php
new file mode 100644
index 0000000..e0b528d
--- /dev/null
+++ b/classes/vendor/74x/composer/installed.php
@@ -0,0 +1,41 @@
+ array(
+ 'name' => 'bfh/verbalfeedback',
+ 'pretty_version' => '1.0.0+no-version-set',
+ 'version' => '1.0.0.0',
+ 'reference' => NULL,
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../../../../dependency_74x',
+ 'aliases' => array(),
+ 'dev' => true,
+ ),
+ 'versions' => array(
+ 'bfh/verbalfeedback' => array(
+ 'pretty_version' => '1.0.0+no-version-set',
+ 'version' => '1.0.0.0',
+ 'reference' => NULL,
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../../../../dependency_74x',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'dallgoot/yaml' => array(
+ 'pretty_version' => 'v0.3.2',
+ 'version' => '0.3.2.0',
+ 'reference' => '505ce0f23b40767d3016ea16e1f101085f3a63c0',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../dallgoot/yaml',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'goat1000/svggraph' => array(
+ 'pretty_version' => '3.20.0',
+ 'version' => '3.20.0.0',
+ 'reference' => '99576c9ad38763b8f10e6b03a9cff1ce32604869',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../goat1000/svggraph',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ ),
+);
diff --git a/classes/vendor/74x/composer/platform_check.php b/classes/vendor/74x/composer/platform_check.php
new file mode 100644
index 0000000..84987dd
--- /dev/null
+++ b/classes/vendor/74x/composer/platform_check.php
@@ -0,0 +1,26 @@
+= 70110)) {
+ $issues[] = 'Your Composer dependencies require a PHP version ">= 7.1.10". You are running ' . PHP_VERSION . '.';
+}
+
+if ($issues) {
+ if (!headers_sent()) {
+ header('HTTP/1.1 500 Internal Server Error');
+ }
+ if (!ini_get('display_errors')) {
+ if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
+ fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
+ } elseif (!headers_sent()) {
+ 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
+ );
+}
diff --git a/classes/vendor/dallgoot/yaml/CHANGELOG.md b/classes/vendor/74x/dallgoot/yaml/CHANGELOG.md
similarity index 100%
rename from classes/vendor/dallgoot/yaml/CHANGELOG.md
rename to classes/vendor/74x/dallgoot/yaml/CHANGELOG.md
diff --git a/classes/vendor/dallgoot/yaml/LICENSE b/classes/vendor/74x/dallgoot/yaml/LICENSE
similarity index 100%
rename from classes/vendor/dallgoot/yaml/LICENSE
rename to classes/vendor/74x/dallgoot/yaml/LICENSE
diff --git a/classes/vendor/dallgoot/yaml/README.md b/classes/vendor/74x/dallgoot/yaml/README.md
similarity index 100%
rename from classes/vendor/dallgoot/yaml/README.md
rename to classes/vendor/74x/dallgoot/yaml/README.md
diff --git a/classes/vendor/dallgoot/yaml/composer.json b/classes/vendor/74x/dallgoot/yaml/composer.json
similarity index 100%
rename from classes/vendor/dallgoot/yaml/composer.json
rename to classes/vendor/74x/dallgoot/yaml/composer.json
diff --git a/classes/vendor/dallgoot/yaml/composer.lock b/classes/vendor/74x/dallgoot/yaml/composer.lock
similarity index 100%
rename from classes/vendor/dallgoot/yaml/composer.lock
rename to classes/vendor/74x/dallgoot/yaml/composer.lock
diff --git a/classes/vendor/dallgoot/yaml/examples/batch_read.php b/classes/vendor/74x/dallgoot/yaml/examples/batch_read.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/examples/batch_read.php
rename to classes/vendor/74x/dallgoot/yaml/examples/batch_read.php
diff --git a/classes/vendor/dallgoot/yaml/examples/compact_notation.php b/classes/vendor/74x/dallgoot/yaml/examples/compact_notation.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/examples/compact_notation.php
rename to classes/vendor/74x/dallgoot/yaml/examples/compact_notation.php
diff --git a/classes/vendor/dallgoot/yaml/examples/config.yml b/classes/vendor/74x/dallgoot/yaml/examples/config.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/examples/config.yml
rename to classes/vendor/74x/dallgoot/yaml/examples/config.yml
diff --git a/classes/vendor/dallgoot/yaml/examples/dummy.yml b/classes/vendor/74x/dallgoot/yaml/examples/dummy.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/examples/dummy.yml
rename to classes/vendor/74x/dallgoot/yaml/examples/dummy.yml
diff --git a/classes/vendor/dallgoot/yaml/examples/load_modify_save.php b/classes/vendor/74x/dallgoot/yaml/examples/load_modify_save.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/examples/load_modify_save.php
rename to classes/vendor/74x/dallgoot/yaml/examples/load_modify_save.php
diff --git a/classes/vendor/dallgoot/yaml/examples/read.php b/classes/vendor/74x/dallgoot/yaml/examples/read.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/examples/read.php
rename to classes/vendor/74x/dallgoot/yaml/examples/read.php
diff --git a/classes/vendor/dallgoot/yaml/examples/references.php b/classes/vendor/74x/dallgoot/yaml/examples/references.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/examples/references.php
rename to classes/vendor/74x/dallgoot/yaml/examples/references.php
diff --git a/classes/vendor/dallgoot/yaml/examples/tags.php b/classes/vendor/74x/dallgoot/yaml/examples/tags.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/examples/tags.php
rename to classes/vendor/74x/dallgoot/yaml/examples/tags.php
diff --git a/classes/vendor/dallgoot/yaml/examples/testing.php b/classes/vendor/74x/dallgoot/yaml/examples/testing.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/examples/testing.php
rename to classes/vendor/74x/dallgoot/yaml/examples/testing.php
diff --git a/classes/vendor/dallgoot/yaml/examples/write.php b/classes/vendor/74x/dallgoot/yaml/examples/write.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/examples/write.php
rename to classes/vendor/74x/dallgoot/yaml/examples/write.php
diff --git a/classes/vendor/dallgoot/yaml/sources/Builder.php b/classes/vendor/74x/dallgoot/yaml/sources/Builder.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/sources/Builder.php
rename to classes/vendor/74x/dallgoot/yaml/sources/Builder.php
diff --git a/classes/vendor/dallgoot/yaml/sources/Dumper.php b/classes/vendor/74x/dallgoot/yaml/sources/Dumper.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/sources/Dumper.php
rename to classes/vendor/74x/dallgoot/yaml/sources/Dumper.php
diff --git a/classes/vendor/dallgoot/yaml/sources/DumperHandlers.php b/classes/vendor/74x/dallgoot/yaml/sources/DumperHandlers.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/sources/DumperHandlers.php
rename to classes/vendor/74x/dallgoot/yaml/sources/DumperHandlers.php
diff --git a/classes/vendor/dallgoot/yaml/sources/Loader.php b/classes/vendor/74x/dallgoot/yaml/sources/Loader.php
similarity index 96%
rename from classes/vendor/dallgoot/yaml/sources/Loader.php
rename to classes/vendor/74x/dallgoot/yaml/sources/Loader.php
index 8193da4..da1d409 100644
--- a/classes/vendor/dallgoot/yaml/sources/Loader.php
+++ b/classes/vendor/74x/dallgoot/yaml/sources/Loader.php
@@ -74,21 +74,10 @@ public function load(string $absolutePath):Loader
throw new \Exception(sprintf(self::EXCEPTION_NO_FILE, $absolutePath));
}
$this->filePath = $absolutePath;
-
- $is_php81 = (version_compare(PHP_VERSION, '8.1.0') >= 0);
-
- // auto_detect_line_endings
$adle_setting = "auto_detect_line_endings";
- if (!$is_php81) {
- ini_set($adle_setting, "true");
- }
-
+ ini_set($adle_setting, "true");
$content = @file($absolutePath, FILE_IGNORE_NEW_LINES);
-
- if (!$is_php81) {
- ini_restore($adle_setting);
- }
-
+ ini_restore($adle_setting);
if (is_bool($content)) {
throw new \Exception(sprintf(self::EXCEPTION_READ_ERROR, $absolutePath));
}
diff --git a/classes/vendor/dallgoot/yaml/sources/NodeFactory.php b/classes/vendor/74x/dallgoot/yaml/sources/NodeFactory.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/sources/NodeFactory.php
rename to classes/vendor/74x/dallgoot/yaml/sources/NodeFactory.php
diff --git a/classes/vendor/dallgoot/yaml/sources/NodeList.php b/classes/vendor/74x/dallgoot/yaml/sources/NodeList.php
similarity index 99%
rename from classes/vendor/dallgoot/yaml/sources/NodeList.php
rename to classes/vendor/74x/dallgoot/yaml/sources/NodeList.php
index 6f02fff..c0143bb 100644
--- a/classes/vendor/dallgoot/yaml/sources/NodeList.php
+++ b/classes/vendor/74x/dallgoot/yaml/sources/NodeList.php
@@ -69,7 +69,7 @@ public function hasContent():bool
return false;
}
- public function push($node): void
+ public function push($node)
{
$type = null;
if ($node instanceof Item ) {
diff --git a/classes/vendor/dallgoot/yaml/sources/Regex.php b/classes/vendor/74x/dallgoot/yaml/sources/Regex.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/sources/Regex.php
rename to classes/vendor/74x/dallgoot/yaml/sources/Regex.php
diff --git a/classes/vendor/dallgoot/yaml/sources/Yaml.php b/classes/vendor/74x/dallgoot/yaml/sources/Yaml.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/sources/Yaml.php
rename to classes/vendor/74x/dallgoot/yaml/sources/Yaml.php
diff --git a/classes/vendor/dallgoot/yaml/sources/YamlProperties.php b/classes/vendor/74x/dallgoot/yaml/sources/YamlProperties.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/sources/YamlProperties.php
rename to classes/vendor/74x/dallgoot/yaml/sources/YamlProperties.php
diff --git a/classes/vendor/dallgoot/yaml/sources/nodes/Anchor.php b/classes/vendor/74x/dallgoot/yaml/sources/nodes/Anchor.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/sources/nodes/Anchor.php
rename to classes/vendor/74x/dallgoot/yaml/sources/nodes/Anchor.php
diff --git a/classes/vendor/dallgoot/yaml/sources/nodes/Blank.php b/classes/vendor/74x/dallgoot/yaml/sources/nodes/Blank.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/sources/nodes/Blank.php
rename to classes/vendor/74x/dallgoot/yaml/sources/nodes/Blank.php
diff --git a/classes/vendor/dallgoot/yaml/sources/nodes/Comment.php b/classes/vendor/74x/dallgoot/yaml/sources/nodes/Comment.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/sources/nodes/Comment.php
rename to classes/vendor/74x/dallgoot/yaml/sources/nodes/Comment.php
diff --git a/classes/vendor/dallgoot/yaml/sources/nodes/CompactMapping.php b/classes/vendor/74x/dallgoot/yaml/sources/nodes/CompactMapping.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/sources/nodes/CompactMapping.php
rename to classes/vendor/74x/dallgoot/yaml/sources/nodes/CompactMapping.php
diff --git a/classes/vendor/dallgoot/yaml/sources/nodes/CompactSequence.php b/classes/vendor/74x/dallgoot/yaml/sources/nodes/CompactSequence.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/sources/nodes/CompactSequence.php
rename to classes/vendor/74x/dallgoot/yaml/sources/nodes/CompactSequence.php
diff --git a/classes/vendor/dallgoot/yaml/sources/nodes/Directive.php b/classes/vendor/74x/dallgoot/yaml/sources/nodes/Directive.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/sources/nodes/Directive.php
rename to classes/vendor/74x/dallgoot/yaml/sources/nodes/Directive.php
diff --git a/classes/vendor/dallgoot/yaml/sources/nodes/DocEnd.php b/classes/vendor/74x/dallgoot/yaml/sources/nodes/DocEnd.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/sources/nodes/DocEnd.php
rename to classes/vendor/74x/dallgoot/yaml/sources/nodes/DocEnd.php
diff --git a/classes/vendor/dallgoot/yaml/sources/nodes/DocStart.php b/classes/vendor/74x/dallgoot/yaml/sources/nodes/DocStart.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/sources/nodes/DocStart.php
rename to classes/vendor/74x/dallgoot/yaml/sources/nodes/DocStart.php
diff --git a/classes/vendor/dallgoot/yaml/sources/nodes/Item.php b/classes/vendor/74x/dallgoot/yaml/sources/nodes/Item.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/sources/nodes/Item.php
rename to classes/vendor/74x/dallgoot/yaml/sources/nodes/Item.php
diff --git a/classes/vendor/dallgoot/yaml/sources/nodes/JSON.php b/classes/vendor/74x/dallgoot/yaml/sources/nodes/JSON.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/sources/nodes/JSON.php
rename to classes/vendor/74x/dallgoot/yaml/sources/nodes/JSON.php
diff --git a/classes/vendor/dallgoot/yaml/sources/nodes/Key.php b/classes/vendor/74x/dallgoot/yaml/sources/nodes/Key.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/sources/nodes/Key.php
rename to classes/vendor/74x/dallgoot/yaml/sources/nodes/Key.php
diff --git a/classes/vendor/dallgoot/yaml/sources/nodes/Literal.php b/classes/vendor/74x/dallgoot/yaml/sources/nodes/Literal.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/sources/nodes/Literal.php
rename to classes/vendor/74x/dallgoot/yaml/sources/nodes/Literal.php
diff --git a/classes/vendor/dallgoot/yaml/sources/nodes/LiteralFolded.php b/classes/vendor/74x/dallgoot/yaml/sources/nodes/LiteralFolded.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/sources/nodes/LiteralFolded.php
rename to classes/vendor/74x/dallgoot/yaml/sources/nodes/LiteralFolded.php
diff --git a/classes/vendor/dallgoot/yaml/sources/nodes/Partial.php b/classes/vendor/74x/dallgoot/yaml/sources/nodes/Partial.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/sources/nodes/Partial.php
rename to classes/vendor/74x/dallgoot/yaml/sources/nodes/Partial.php
diff --git a/classes/vendor/dallgoot/yaml/sources/nodes/Quoted.php b/classes/vendor/74x/dallgoot/yaml/sources/nodes/Quoted.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/sources/nodes/Quoted.php
rename to classes/vendor/74x/dallgoot/yaml/sources/nodes/Quoted.php
diff --git a/classes/vendor/dallgoot/yaml/sources/nodes/Root.php b/classes/vendor/74x/dallgoot/yaml/sources/nodes/Root.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/sources/nodes/Root.php
rename to classes/vendor/74x/dallgoot/yaml/sources/nodes/Root.php
diff --git a/classes/vendor/dallgoot/yaml/sources/nodes/Scalar.php b/classes/vendor/74x/dallgoot/yaml/sources/nodes/Scalar.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/sources/nodes/Scalar.php
rename to classes/vendor/74x/dallgoot/yaml/sources/nodes/Scalar.php
diff --git a/classes/vendor/dallgoot/yaml/sources/nodes/SetKey.php b/classes/vendor/74x/dallgoot/yaml/sources/nodes/SetKey.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/sources/nodes/SetKey.php
rename to classes/vendor/74x/dallgoot/yaml/sources/nodes/SetKey.php
diff --git a/classes/vendor/dallgoot/yaml/sources/nodes/SetValue.php b/classes/vendor/74x/dallgoot/yaml/sources/nodes/SetValue.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/sources/nodes/SetValue.php
rename to classes/vendor/74x/dallgoot/yaml/sources/nodes/SetValue.php
diff --git a/classes/vendor/dallgoot/yaml/sources/nodes/Tag.php b/classes/vendor/74x/dallgoot/yaml/sources/nodes/Tag.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/sources/nodes/Tag.php
rename to classes/vendor/74x/dallgoot/yaml/sources/nodes/Tag.php
diff --git a/classes/vendor/dallgoot/yaml/sources/nodes/abstract/Actions.php b/classes/vendor/74x/dallgoot/yaml/sources/nodes/abstract/Actions.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/sources/nodes/abstract/Actions.php
rename to classes/vendor/74x/dallgoot/yaml/sources/nodes/abstract/Actions.php
diff --git a/classes/vendor/dallgoot/yaml/sources/nodes/abstract/Literals.php b/classes/vendor/74x/dallgoot/yaml/sources/nodes/abstract/Literals.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/sources/nodes/abstract/Literals.php
rename to classes/vendor/74x/dallgoot/yaml/sources/nodes/abstract/Literals.php
diff --git a/classes/vendor/dallgoot/yaml/sources/nodes/abstract/NodeGeneric.php b/classes/vendor/74x/dallgoot/yaml/sources/nodes/abstract/NodeGeneric.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/sources/nodes/abstract/NodeGeneric.php
rename to classes/vendor/74x/dallgoot/yaml/sources/nodes/abstract/NodeGeneric.php
diff --git a/classes/vendor/dallgoot/yaml/sources/tag/CoreSchema.php b/classes/vendor/74x/dallgoot/yaml/sources/tag/CoreSchema.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/sources/tag/CoreSchema.php
rename to classes/vendor/74x/dallgoot/yaml/sources/tag/CoreSchema.php
diff --git a/classes/vendor/dallgoot/yaml/sources/tag/SchemaInterface.php b/classes/vendor/74x/dallgoot/yaml/sources/tag/SchemaInterface.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/sources/tag/SchemaInterface.php
rename to classes/vendor/74x/dallgoot/yaml/sources/tag/SchemaInterface.php
diff --git a/classes/vendor/dallgoot/yaml/sources/tag/SymfonySchema.php b/classes/vendor/74x/dallgoot/yaml/sources/tag/SymfonySchema.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/sources/tag/SymfonySchema.php
rename to classes/vendor/74x/dallgoot/yaml/sources/tag/SymfonySchema.php
diff --git a/classes/vendor/dallgoot/yaml/sources/tag/TagFactory.php b/classes/vendor/74x/dallgoot/yaml/sources/tag/TagFactory.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/sources/tag/TagFactory.php
rename to classes/vendor/74x/dallgoot/yaml/sources/tag/TagFactory.php
diff --git a/classes/vendor/dallgoot/yaml/sources/tag/notes.yml b/classes/vendor/74x/dallgoot/yaml/sources/tag/notes.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/sources/tag/notes.yml
rename to classes/vendor/74x/dallgoot/yaml/sources/tag/notes.yml
diff --git a/classes/vendor/dallgoot/yaml/sources/types/Compact.php b/classes/vendor/74x/dallgoot/yaml/sources/types/Compact.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/sources/types/Compact.php
rename to classes/vendor/74x/dallgoot/yaml/sources/types/Compact.php
diff --git a/classes/vendor/dallgoot/yaml/sources/types/Tagged.php b/classes/vendor/74x/dallgoot/yaml/sources/types/Tagged.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/sources/types/Tagged.php
rename to classes/vendor/74x/dallgoot/yaml/sources/types/Tagged.php
diff --git a/classes/vendor/dallgoot/yaml/sources/types/YamlObject.php b/classes/vendor/74x/dallgoot/yaml/sources/types/YamlObject.php
similarity index 99%
rename from classes/vendor/dallgoot/yaml/sources/types/YamlObject.php
rename to classes/vendor/74x/dallgoot/yaml/sources/types/YamlObject.php
index d742671..8e77ac5 100644
--- a/classes/vendor/dallgoot/yaml/sources/types/YamlObject.php
+++ b/classes/vendor/74x/dallgoot/yaml/sources/types/YamlObject.php
@@ -187,7 +187,7 @@ public function isTagged()
*
* @return mixed Array (of object properties or keys) OR string if YAML object only contains LITTERAL (in self::value)
*/
- public function jsonSerialize(): mixed
+ public function jsonSerialize()
{
$prop = get_object_vars($this);
unset($prop["__yaml__object__api"]);
diff --git a/classes/vendor/dallgoot/yaml/test_all_versions.sh b/classes/vendor/74x/dallgoot/yaml/test_all_versions.sh
similarity index 100%
rename from classes/vendor/dallgoot/yaml/test_all_versions.sh
rename to classes/vendor/74x/dallgoot/yaml/test_all_versions.sh
diff --git a/classes/vendor/dallgoot/yaml/tests/CasesTest.php b/classes/vendor/74x/dallgoot/yaml/tests/CasesTest.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/CasesTest.php
rename to classes/vendor/74x/dallgoot/yaml/tests/CasesTest.php
diff --git a/classes/vendor/dallgoot/yaml/tests/DumpingTest.php b/classes/vendor/74x/dallgoot/yaml/tests/DumpingTest.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/DumpingTest.php
rename to classes/vendor/74x/dallgoot/yaml/tests/DumpingTest.php
diff --git a/classes/vendor/dallgoot/yaml/tests/SymfonyYamlTest.php b/classes/vendor/74x/dallgoot/yaml/tests/SymfonyYamlTest.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/SymfonyYamlTest.php
rename to classes/vendor/74x/dallgoot/yaml/tests/SymfonyYamlTest.php
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/dumping/array.php b/classes/vendor/74x/dallgoot/yaml/tests/cases/dumping/array.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/dumping/array.php
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/dumping/array.php
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/dumping/array_with_alphakey.php b/classes/vendor/74x/dallgoot/yaml/tests/cases/dumping/array_with_alphakey.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/dumping/array_with_alphakey.php
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/dumping/array_with_alphakey.php
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/dumping/compact_array.php b/classes/vendor/74x/dallgoot/yaml/tests/cases/dumping/compact_array.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/dumping/compact_array.php
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/dumping/compact_array.php
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/dumping/compact_array_in_object.php b/classes/vendor/74x/dallgoot/yaml/tests/cases/dumping/compact_array_in_object.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/dumping/compact_array_in_object.php
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/dumping/compact_array_in_object.php
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/dumping/compact_object.php b/classes/vendor/74x/dallgoot/yaml/tests/cases/dumping/compact_object.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/dumping/compact_object.php
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/dumping/compact_object.php
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/dumping/compact_object_in_array.php b/classes/vendor/74x/dallgoot/yaml/tests/cases/dumping/compact_object_in_array.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/dumping/compact_object_in_array.php
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/dumping/compact_object_in_array.php
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/dumping/dateTime.php b/classes/vendor/74x/dallgoot/yaml/tests/cases/dumping/dateTime.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/dumping/dateTime.php
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/dumping/dateTime.php
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/dumping/floats_double.php b/classes/vendor/74x/dallgoot/yaml/tests/cases/dumping/floats_double.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/dumping/floats_double.php
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/dumping/floats_double.php
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/dumping/multidoc_yamlObject.php b/classes/vendor/74x/dallgoot/yaml/tests/cases/dumping/multidoc_yamlObject.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/dumping/multidoc_yamlObject.php
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/dumping/multidoc_yamlObject.php
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/dumping/stdObject.php b/classes/vendor/74x/dallgoot/yaml/tests/cases/dumping/stdObject.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/dumping/stdObject.php
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/dumping/stdObject.php
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/dumping/unicodeString.php b/classes/vendor/74x/dallgoot/yaml/tests/cases/dumping/unicodeString.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/dumping/unicodeString.php
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/dumping/unicodeString.php
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/dumping/yamlObject_indices.php b/classes/vendor/74x/dallgoot/yaml/tests/cases/dumping/yamlObject_indices.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/dumping/yamlObject_indices.php
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/dumping/yamlObject_indices.php
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/dumping/yamlObject_properties.php b/classes/vendor/74x/dallgoot/yaml/tests/cases/dumping/yamlObject_properties.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/dumping/yamlObject_properties.php
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/dumping/yamlObject_properties.php
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_01.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_01.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_01.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_01.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_02.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_02.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_02.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_02.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_03.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_03.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_03.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_03.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_04.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_04.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_04.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_04.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_05.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_05.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_05.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_05.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_06.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_06.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_06.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_06.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_07.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_07.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_07.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_07.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_08.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_08.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_08.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_08.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_09.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_09.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_09.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_09.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_10.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_10.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_10.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_10.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_11.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_11.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_11.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_11.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_12.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_12.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_12.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_12.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_13.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_13.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_13.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_13.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_14.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_14.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_14.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_14.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_15.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_15.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_15.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_15.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_16.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_16.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_16.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_16.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_17.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_17.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_17.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_17.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_18.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_18.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_18.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_18.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_19.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_19.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_19.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_19.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_20.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_20.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_20.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_20.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_21.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_21.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_21.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_21.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_22.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_22.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_22.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_22.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_23.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_23.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_23.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_23.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_24.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_24.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_24.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_24.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_25.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_25.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_25.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_25.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_26.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_26.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_26.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_26.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_27.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_27.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_27.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_27.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_28.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_28.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/examples/Example_2_28.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/examples/Example_2_28.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/failing/compact_keyvalue_with_no_space.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/failing/compact_keyvalue_with_no_space.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/failing/compact_keyvalue_with_no_space.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/failing/compact_keyvalue_with_no_space.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/failing/compact_with_emptystring_as_key.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/failing/compact_with_emptystring_as_key.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/failing/compact_with_emptystring_as_key.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/failing/compact_with_emptystring_as_key.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/failing/compact_with_invalid_reference.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/failing/compact_with_invalid_reference.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/failing/compact_with_invalid_reference.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/failing/compact_with_invalid_reference.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/failing/compact_with_invalid_reference_and_comment.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/failing/compact_with_invalid_reference_and_comment.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/failing/compact_with_invalid_reference_and_comment.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/failing/compact_with_invalid_reference_and_comment.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/failing/compact_with_no_closing_brace.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/failing/compact_with_no_closing_brace.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/failing/compact_with_no_closing_brace.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/failing/compact_with_no_closing_brace.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/failing/compact_with_no_keyvalue_pair.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/failing/compact_with_no_keyvalue_pair.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/failing/compact_with_no_keyvalue_pair.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/failing/compact_with_no_keyvalue_pair.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/failing/invalid_quoting_in_compact.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/failing/invalid_quoting_in_compact.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/failing/invalid_quoting_in_compact.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/failing/invalid_quoting_in_compact.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/failing/key_in_key.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/failing/key_in_key.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/failing/key_in_key.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/failing/key_in_key.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/failing/litteral_folded_with_immediate_value.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/failing/litteral_folded_with_immediate_value.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/failing/litteral_folded_with_immediate_value.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/failing/litteral_folded_with_immediate_value.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/failing/litteral_with_immediate_value.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/failing/litteral_with_immediate_value.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/failing/litteral_with_immediate_value.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/failing/litteral_with_immediate_value.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/failing/map_key_with_scalar_and_sequence.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/failing/map_key_with_scalar_and_sequence.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/failing/map_key_with_scalar_and_sequence.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/failing/map_key_with_scalar_and_sequence.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/failing/map_with_compact_with_duplicates.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/failing/map_with_compact_with_duplicates.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/failing/map_with_compact_with_duplicates.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/failing/map_with_compact_with_duplicates.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/failing/map_with_embed_compact_and_comment.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/failing/map_with_embed_compact_and_comment.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/failing/map_with_embed_compact_and_comment.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/failing/map_with_embed_compact_and_comment.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/failing/seq_items_with_multiline_scalar_and_key.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/failing/seq_items_with_multiline_scalar_and_key.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/failing/seq_items_with_multiline_scalar_and_key.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/failing/seq_items_with_multiline_scalar_and_key.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/failing/seq_items_with_scalar_and_key.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/failing/seq_items_with_scalar_and_key.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/failing/seq_items_with_scalar_and_key.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/failing/seq_items_with_scalar_and_key.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/failing/sequence_items_with_no_space.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/failing/sequence_items_with_no_space.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/failing/sequence_items_with_no_space.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/failing/sequence_items_with_no_space.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/failing/special_chars_with_no_sense.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/failing/special_chars_with_no_sense.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/failing/special_chars_with_no_sense.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/failing/special_chars_with_no_sense.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/failing/string_alone_with_indented_key.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/failing/string_alone_with_indented_key.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/failing/string_alone_with_indented_key.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/failing/string_alone_with_indented_key.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/failing/unescaped_double_quotes.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/failing/unescaped_double_quotes.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/failing/unescaped_double_quotes.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/failing/unescaped_double_quotes.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/failing/unescaped_simple_quotes.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/failing/unescaped_simple_quotes.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/failing/unescaped_simple_quotes.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/failing/unescaped_simple_quotes.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/blockChomping.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/blockChomping.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/blockChomping.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/blockChomping.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/blockChompingWithInsideBlank.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/blockChompingWithInsideBlank.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/blockChompingWithInsideBlank.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/blockChompingWithInsideBlank.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/blockChompingWithKeep.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/blockChompingWithKeep.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/blockChompingWithKeep.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/blockChompingWithKeep.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/blockChompingWithKeepAndTrailing.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/blockChompingWithKeepAndTrailing.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/blockChompingWithKeepAndTrailing.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/blockChompingWithKeepAndTrailing.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/blockChompingWithStripAndTrailing.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/blockChompingWithStripAndTrailing.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/blockChompingWithStripAndTrailing.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/blockChompingWithStripAndTrailing.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/comment_at_first_and_inside_map.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/comment_at_first_and_inside_map.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/comment_at_first_and_inside_map.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/comment_at_first_and_inside_map.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/comment_between_keys.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/comment_between_keys.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/comment_between_keys.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/comment_between_keys.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/comment_inside_litteral_in_subkey.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/comment_inside_litteral_in_subkey.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/comment_inside_litteral_in_subkey.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/comment_inside_litteral_in_subkey.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/comment_with_a_colon.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/comment_with_a_colon.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/comment_with_a_colon.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/comment_with_a_colon.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/comments_inside_litteral.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/comments_inside_litteral.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/comments_inside_litteral.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/comments_inside_litteral.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/compact_array_inside_mapping.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/compact_array_inside_mapping.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/compact_array_inside_mapping.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/compact_array_inside_mapping.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/compact_in_a_key_separated_by_comment.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/compact_in_a_key_separated_by_comment.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/compact_in_a_key_separated_by_comment.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/compact_in_a_key_separated_by_comment.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/complex_mapping.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/complex_mapping.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/complex_mapping.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/complex_mapping.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/complex_mapping_in_item.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/complex_mapping_in_item.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/complex_mapping_in_item.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/complex_mapping_in_item.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/complex_mapping_in_key.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/complex_mapping_in_key.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/complex_mapping_in_key.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/complex_mapping_in_key.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/directive_at_start.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/directive_at_start.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/directive_at_start.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/directive_at_start.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/encoding_non_utf8.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/encoding_non_utf8.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/encoding_non_utf8.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/encoding_non_utf8.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/endOfDocumentMapping.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/endOfDocumentMapping.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/endOfDocumentMapping.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/endOfDocumentMapping.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/endOfDocumentSequence.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/endOfDocumentSequence.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/endOfDocumentSequence.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/endOfDocumentSequence.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/folded_with_alpha_keys.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/folded_with_alpha_keys.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/folded_with_alpha_keys.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/folded_with_alpha_keys.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/key_separated_by_blank_line.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/key_separated_by_blank_line.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/key_separated_by_blank_line.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/key_separated_by_blank_line.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/keysWithTabs.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/keysWithTabs.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/keysWithTabs.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/keysWithTabs.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/literalFolded_clip.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/literalFolded_clip.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/literalFolded_clip.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/literalFolded_clip.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/literalFolded_strip_trailing.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/literalFolded_strip_trailing.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/literalFolded_strip_trailing.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/literalFolded_strip_trailing.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/literal_empty_with_blank.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/literal_empty_with_blank.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/literal_empty_with_blank.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/literal_empty_with_blank.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/literal_keep_trailing_leading.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/literal_keep_trailing_leading.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/literal_keep_trailing_leading.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/literal_keep_trailing_leading.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/literal_with_comment_at_lastline.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/literal_with_comment_at_lastline.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/literal_with_comment_at_lastline.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/literal_with_comment_at_lastline.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/literalfolded_keep_trailing.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/literalfolded_keep_trailing.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/literalfolded_keep_trailing.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/literalfolded_keep_trailing.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/map_and_items_mixing.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/map_and_items_mixing.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/map_and_items_mixing.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/map_and_items_mixing.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/map_in_an_empty_item.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/map_in_an_empty_item.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/map_in_an_empty_item.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/map_in_an_empty_item.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/map_with_boolean_keys.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/map_with_boolean_keys.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/map_with_boolean_keys.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/map_with_boolean_keys.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/map_with_comment_inside_item.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/map_with_comment_inside_item.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/map_with_comment_inside_item.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/map_with_comment_inside_item.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/map_with_duplicate_keys.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/map_with_duplicate_keys.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/map_with_duplicate_keys.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/map_with_duplicate_keys.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/map_with_float_keys.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/map_with_float_keys.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/map_with_float_keys.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/map_with_float_keys.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/map_with_number_keys.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/map_with_number_keys.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/map_with_number_keys.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/map_with_number_keys.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/map_with_quoted_number_keys.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/map_with_quoted_number_keys.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/map_with_quoted_number_keys.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/map_with_quoted_number_keys.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/map_with_seq_and_embed_comment.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/map_with_seq_and_embed_comment.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/map_with_seq_and_embed_comment.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/map_with_seq_and_embed_comment.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/multidoc_empty_doc_in_between.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/multidoc_empty_doc_in_between.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/multidoc_empty_doc_in_between.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/multidoc_empty_doc_in_between.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/multidoc_mapping.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/multidoc_mapping.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/multidoc_mapping.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/multidoc_mapping.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/multidoc_sequence.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/multidoc_sequence.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/multidoc_sequence.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/multidoc_sequence.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/multiline_indented.keyslookalike.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/multiline_indented.keyslookalike.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/multiline_indented.keyslookalike.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/multiline_indented.keyslookalike.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/multiline_quoted.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/multiline_quoted.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/multiline_quoted.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/multiline_quoted.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/multiline_quoted_in_subkey.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/multiline_quoted_in_subkey.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/multiline_quoted_in_subkey.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/multiline_quoted_in_subkey.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/multiline_quoted_with_blank.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/multiline_quoted_with_blank.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/multiline_quoted_with_blank.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/multiline_quoted_with_blank.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/multiline_quoted_with_slash.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/multiline_quoted_with_slash.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/multiline_quoted_with_slash.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/multiline_quoted_with_slash.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/multiline_unquoted.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/multiline_unquoted.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/multiline_unquoted.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/multiline_unquoted.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/multiline_unquoted_in_seqitem.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/multiline_unquoted_in_seqitem.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/multiline_unquoted_in_seqitem.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/multiline_unquoted_in_seqitem.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/multiline_with_boolean_strings.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/multiline_with_boolean_strings.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/multiline_with_boolean_strings.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/multiline_with_boolean_strings.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/references_inside_compact.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/references_inside_compact.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/references_inside_compact.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/references_inside_compact.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/references_inside_merged_key.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/references_inside_merged_key.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/references_inside_merged_key.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/references_inside_merged_key.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/references_with_merge.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/references_with_merge.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/references_with_merge.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/references_with_merge.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/references_with_merged_keys_inside_compact.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/references_with_merged_keys_inside_compact.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/references_with_merged_keys_inside_compact.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/references_with_merged_keys_inside_compact.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/references_with_selfreference.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/references_with_selfreference.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/references_with_selfreference.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/references_with_selfreference.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/sequence_item_with_multiple_keys.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/sequence_item_with_multiple_keys.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/sequence_item_with_multiple_keys.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/sequence_item_with_multiple_keys.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/sequence_with_duplicate_keys.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/sequence_with_duplicate_keys.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/sequence_with_duplicate_keys.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/sequence_with_duplicate_keys.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/sequence_with_linefeed_and_key.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/sequence_with_linefeed_and_key.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/sequence_with_linefeed_and_key.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/sequence_with_linefeed_and_key.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/tag_symfony_phpobject.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/tag_symfony_phpobject.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/tag_symfony_phpobject.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/tag_symfony_phpobject.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/tags_as_casting.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/tags_as_casting.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/tags_as_casting.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/tags_as_casting.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/tags_as_keys.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/tags_as_keys.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/tags_as_keys.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/tags_as_keys.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/tags_in_compact.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/tags_in_compact.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/tags_in_compact.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/tags_in_compact.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/tags_in_item_with_sequence.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/tags_in_item_with_sequence.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/tags_in_item_with_sequence.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/tags_in_item_with_sequence.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/tags_in_litteral.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/tags_in_litteral.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/tags_in_litteral.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/tags_in_litteral.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/tags_in_mapping.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/tags_in_mapping.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/tags_in_mapping.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/tags_in_mapping.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/tags_inline_long.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/tags_inline_long.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/tags_inline_long.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/tags_inline_long.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/tags_with_quotes.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/tags_with_quotes.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/tags_with_quotes.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/tags_with_quotes.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/cases/parsing/yaml_in_literal_folded.yml b/classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/yaml_in_literal_folded.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/cases/parsing/yaml_in_literal_folded.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/cases/parsing/yaml_in_literal_folded.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/definitions/dumping_tests.yml b/classes/vendor/74x/dallgoot/yaml/tests/definitions/dumping_tests.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/definitions/dumping_tests.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/definitions/dumping_tests.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/definitions/examples_tests.yml b/classes/vendor/74x/dallgoot/yaml/tests/definitions/examples_tests.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/definitions/examples_tests.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/definitions/examples_tests.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/definitions/failing_tests.yml b/classes/vendor/74x/dallgoot/yaml/tests/definitions/failing_tests.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/definitions/failing_tests.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/definitions/failing_tests.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/definitions/parsing_tests.yml b/classes/vendor/74x/dallgoot/yaml/tests/definitions/parsing_tests.yml
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/definitions/parsing_tests.yml
rename to classes/vendor/74x/dallgoot/yaml/tests/definitions/parsing_tests.yml
diff --git a/classes/vendor/dallgoot/yaml/tests/units/BuilderTest.php b/classes/vendor/74x/dallgoot/yaml/tests/units/BuilderTest.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/units/BuilderTest.php
rename to classes/vendor/74x/dallgoot/yaml/tests/units/BuilderTest.php
diff --git a/classes/vendor/dallgoot/yaml/tests/units/DumperHandlersTest.php b/classes/vendor/74x/dallgoot/yaml/tests/units/DumperHandlersTest.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/units/DumperHandlersTest.php
rename to classes/vendor/74x/dallgoot/yaml/tests/units/DumperHandlersTest.php
diff --git a/classes/vendor/dallgoot/yaml/tests/units/DumperTest.php b/classes/vendor/74x/dallgoot/yaml/tests/units/DumperTest.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/units/DumperTest.php
rename to classes/vendor/74x/dallgoot/yaml/tests/units/DumperTest.php
diff --git a/classes/vendor/dallgoot/yaml/tests/units/LoaderTest.php b/classes/vendor/74x/dallgoot/yaml/tests/units/LoaderTest.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/units/LoaderTest.php
rename to classes/vendor/74x/dallgoot/yaml/tests/units/LoaderTest.php
diff --git a/classes/vendor/dallgoot/yaml/tests/units/NodeFactoryTest.php b/classes/vendor/74x/dallgoot/yaml/tests/units/NodeFactoryTest.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/units/NodeFactoryTest.php
rename to classes/vendor/74x/dallgoot/yaml/tests/units/NodeFactoryTest.php
diff --git a/classes/vendor/dallgoot/yaml/tests/units/NodeListTest.php b/classes/vendor/74x/dallgoot/yaml/tests/units/NodeListTest.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/units/NodeListTest.php
rename to classes/vendor/74x/dallgoot/yaml/tests/units/NodeListTest.php
diff --git a/classes/vendor/dallgoot/yaml/tests/units/RegexTest.php b/classes/vendor/74x/dallgoot/yaml/tests/units/RegexTest.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/units/RegexTest.php
rename to classes/vendor/74x/dallgoot/yaml/tests/units/RegexTest.php
diff --git a/classes/vendor/dallgoot/yaml/tests/units/YamlTest.php b/classes/vendor/74x/dallgoot/yaml/tests/units/YamlTest.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/units/YamlTest.php
rename to classes/vendor/74x/dallgoot/yaml/tests/units/YamlTest.php
diff --git a/classes/vendor/dallgoot/yaml/tests/units/nodes/AnchorTest.php b/classes/vendor/74x/dallgoot/yaml/tests/units/nodes/AnchorTest.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/units/nodes/AnchorTest.php
rename to classes/vendor/74x/dallgoot/yaml/tests/units/nodes/AnchorTest.php
diff --git a/classes/vendor/dallgoot/yaml/tests/units/nodes/BlankTest.php b/classes/vendor/74x/dallgoot/yaml/tests/units/nodes/BlankTest.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/units/nodes/BlankTest.php
rename to classes/vendor/74x/dallgoot/yaml/tests/units/nodes/BlankTest.php
diff --git a/classes/vendor/dallgoot/yaml/tests/units/nodes/CommentTest.php b/classes/vendor/74x/dallgoot/yaml/tests/units/nodes/CommentTest.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/units/nodes/CommentTest.php
rename to classes/vendor/74x/dallgoot/yaml/tests/units/nodes/CommentTest.php
diff --git a/classes/vendor/dallgoot/yaml/tests/units/nodes/CompactMappingTest.php b/classes/vendor/74x/dallgoot/yaml/tests/units/nodes/CompactMappingTest.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/units/nodes/CompactMappingTest.php
rename to classes/vendor/74x/dallgoot/yaml/tests/units/nodes/CompactMappingTest.php
diff --git a/classes/vendor/dallgoot/yaml/tests/units/nodes/CompactSequenceTest.php b/classes/vendor/74x/dallgoot/yaml/tests/units/nodes/CompactSequenceTest.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/units/nodes/CompactSequenceTest.php
rename to classes/vendor/74x/dallgoot/yaml/tests/units/nodes/CompactSequenceTest.php
diff --git a/classes/vendor/dallgoot/yaml/tests/units/nodes/DirectiveTest.php b/classes/vendor/74x/dallgoot/yaml/tests/units/nodes/DirectiveTest.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/units/nodes/DirectiveTest.php
rename to classes/vendor/74x/dallgoot/yaml/tests/units/nodes/DirectiveTest.php
diff --git a/classes/vendor/dallgoot/yaml/tests/units/nodes/DocEndTest.php b/classes/vendor/74x/dallgoot/yaml/tests/units/nodes/DocEndTest.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/units/nodes/DocEndTest.php
rename to classes/vendor/74x/dallgoot/yaml/tests/units/nodes/DocEndTest.php
diff --git a/classes/vendor/dallgoot/yaml/tests/units/nodes/DocStartTest.php b/classes/vendor/74x/dallgoot/yaml/tests/units/nodes/DocStartTest.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/units/nodes/DocStartTest.php
rename to classes/vendor/74x/dallgoot/yaml/tests/units/nodes/DocStartTest.php
diff --git a/classes/vendor/dallgoot/yaml/tests/units/nodes/ItemTest.php b/classes/vendor/74x/dallgoot/yaml/tests/units/nodes/ItemTest.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/units/nodes/ItemTest.php
rename to classes/vendor/74x/dallgoot/yaml/tests/units/nodes/ItemTest.php
diff --git a/classes/vendor/dallgoot/yaml/tests/units/nodes/JSONTest.php b/classes/vendor/74x/dallgoot/yaml/tests/units/nodes/JSONTest.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/units/nodes/JSONTest.php
rename to classes/vendor/74x/dallgoot/yaml/tests/units/nodes/JSONTest.php
diff --git a/classes/vendor/dallgoot/yaml/tests/units/nodes/KeyTest.php b/classes/vendor/74x/dallgoot/yaml/tests/units/nodes/KeyTest.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/units/nodes/KeyTest.php
rename to classes/vendor/74x/dallgoot/yaml/tests/units/nodes/KeyTest.php
diff --git a/classes/vendor/dallgoot/yaml/tests/units/nodes/LiteralFoldedTest.php b/classes/vendor/74x/dallgoot/yaml/tests/units/nodes/LiteralFoldedTest.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/units/nodes/LiteralFoldedTest.php
rename to classes/vendor/74x/dallgoot/yaml/tests/units/nodes/LiteralFoldedTest.php
diff --git a/classes/vendor/dallgoot/yaml/tests/units/nodes/LiteralTest.php b/classes/vendor/74x/dallgoot/yaml/tests/units/nodes/LiteralTest.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/units/nodes/LiteralTest.php
rename to classes/vendor/74x/dallgoot/yaml/tests/units/nodes/LiteralTest.php
diff --git a/classes/vendor/dallgoot/yaml/tests/units/nodes/PartialTest.php b/classes/vendor/74x/dallgoot/yaml/tests/units/nodes/PartialTest.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/units/nodes/PartialTest.php
rename to classes/vendor/74x/dallgoot/yaml/tests/units/nodes/PartialTest.php
diff --git a/classes/vendor/dallgoot/yaml/tests/units/nodes/QuotedTest.php b/classes/vendor/74x/dallgoot/yaml/tests/units/nodes/QuotedTest.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/units/nodes/QuotedTest.php
rename to classes/vendor/74x/dallgoot/yaml/tests/units/nodes/QuotedTest.php
diff --git a/classes/vendor/dallgoot/yaml/tests/units/nodes/RootTest.php b/classes/vendor/74x/dallgoot/yaml/tests/units/nodes/RootTest.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/units/nodes/RootTest.php
rename to classes/vendor/74x/dallgoot/yaml/tests/units/nodes/RootTest.php
diff --git a/classes/vendor/dallgoot/yaml/tests/units/nodes/ScalarTest.php b/classes/vendor/74x/dallgoot/yaml/tests/units/nodes/ScalarTest.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/units/nodes/ScalarTest.php
rename to classes/vendor/74x/dallgoot/yaml/tests/units/nodes/ScalarTest.php
diff --git a/classes/vendor/dallgoot/yaml/tests/units/nodes/SetKeyTest.php b/classes/vendor/74x/dallgoot/yaml/tests/units/nodes/SetKeyTest.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/units/nodes/SetKeyTest.php
rename to classes/vendor/74x/dallgoot/yaml/tests/units/nodes/SetKeyTest.php
diff --git a/classes/vendor/dallgoot/yaml/tests/units/nodes/SetValueTest.php b/classes/vendor/74x/dallgoot/yaml/tests/units/nodes/SetValueTest.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/units/nodes/SetValueTest.php
rename to classes/vendor/74x/dallgoot/yaml/tests/units/nodes/SetValueTest.php
diff --git a/classes/vendor/dallgoot/yaml/tests/units/nodes/TagTest.php b/classes/vendor/74x/dallgoot/yaml/tests/units/nodes/TagTest.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/units/nodes/TagTest.php
rename to classes/vendor/74x/dallgoot/yaml/tests/units/nodes/TagTest.php
diff --git a/classes/vendor/dallgoot/yaml/tests/units/nodes/abstract/ActionsTest.php b/classes/vendor/74x/dallgoot/yaml/tests/units/nodes/abstract/ActionsTest.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/units/nodes/abstract/ActionsTest.php
rename to classes/vendor/74x/dallgoot/yaml/tests/units/nodes/abstract/ActionsTest.php
diff --git a/classes/vendor/dallgoot/yaml/tests/units/nodes/abstract/LiteralsTest.php b/classes/vendor/74x/dallgoot/yaml/tests/units/nodes/abstract/LiteralsTest.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/units/nodes/abstract/LiteralsTest.php
rename to classes/vendor/74x/dallgoot/yaml/tests/units/nodes/abstract/LiteralsTest.php
diff --git a/classes/vendor/dallgoot/yaml/tests/units/nodes/abstract/NodeGenericTest.php b/classes/vendor/74x/dallgoot/yaml/tests/units/nodes/abstract/NodeGenericTest.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/units/nodes/abstract/NodeGenericTest.php
rename to classes/vendor/74x/dallgoot/yaml/tests/units/nodes/abstract/NodeGenericTest.php
diff --git a/classes/vendor/dallgoot/yaml/tests/units/tag/TagFactoryTest.php b/classes/vendor/74x/dallgoot/yaml/tests/units/tag/TagFactoryTest.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/units/tag/TagFactoryTest.php
rename to classes/vendor/74x/dallgoot/yaml/tests/units/tag/TagFactoryTest.php
diff --git a/classes/vendor/dallgoot/yaml/tests/units/types/CompactTest.php b/classes/vendor/74x/dallgoot/yaml/tests/units/types/CompactTest.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/units/types/CompactTest.php
rename to classes/vendor/74x/dallgoot/yaml/tests/units/types/CompactTest.php
diff --git a/classes/vendor/dallgoot/yaml/tests/units/types/TaggedTest.php b/classes/vendor/74x/dallgoot/yaml/tests/units/types/TaggedTest.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/units/types/TaggedTest.php
rename to classes/vendor/74x/dallgoot/yaml/tests/units/types/TaggedTest.php
diff --git a/classes/vendor/dallgoot/yaml/tests/units/types/YamlObjectTest.php b/classes/vendor/74x/dallgoot/yaml/tests/units/types/YamlObjectTest.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/units/types/YamlObjectTest.php
rename to classes/vendor/74x/dallgoot/yaml/tests/units/types/YamlObjectTest.php
diff --git a/classes/vendor/dallgoot/yaml/tests/xdebug-filter.php b/classes/vendor/74x/dallgoot/yaml/tests/xdebug-filter.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/tests/xdebug-filter.php
rename to classes/vendor/74x/dallgoot/yaml/tests/xdebug-filter.php
diff --git a/classes/vendor/goat1000/svggraph/.gitattributes b/classes/vendor/74x/goat1000/svggraph/.gitattributes
similarity index 100%
rename from classes/vendor/goat1000/svggraph/.gitattributes
rename to classes/vendor/74x/goat1000/svggraph/.gitattributes
diff --git a/classes/vendor/74x/goat1000/svggraph/Algebraic.php b/classes/vendor/74x/goat1000/svggraph/Algebraic.php
new file mode 100644
index 0000000..e8ac02a
--- /dev/null
+++ b/classes/vendor/74x/goat1000/svggraph/Algebraic.php
@@ -0,0 +1,106 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * Class for algebraic functions
+ */
+class Algebraic {
+
+ private $type = 'straight';
+ private $coeffs = [0, 1];
+
+ public function __construct($type)
+ {
+ $this->type = $type;
+ }
+
+ /**
+ * Sets the coefficients in order, lowest power first
+ */
+ public function setCoefficients(array $coefficients)
+ {
+ $this->coeffs = $coefficients;
+ }
+
+ /**
+ * Returns the y value for a + bx + cx^2 ...
+ */
+ public function __invoke($x)
+ {
+ $val = 0;
+ foreach($this->coeffs as $p => $c) {
+ switch($p) {
+ case 0: $val = bcadd($val, $c);
+ break;
+ case 1: $val = bcadd($val, bcmul($c, $x));
+ break;
+ default:
+ $val = bcadd($val, bcmul($c, bcpow($x, $p)));
+ break;
+ }
+ }
+ return $val;
+ }
+
+ /**
+ * Creates a row of the vandermonde matrix
+ */
+ public function vandermonde($x)
+ {
+ $t = $this->type;
+ return $this->{$t}($x);
+ }
+
+ private function straight($x)
+ {
+ return [$x];
+ }
+
+ private function quadratic($x)
+ {
+ return [$x, bcmul($x, $x)];
+ }
+
+ private function cubic($x)
+ {
+ $res = [$x, bcmul($x, $x)];
+ $res[] = bcmul($res[1], $x);
+ return $res;
+ }
+
+ private function quartic($x)
+ {
+ $res = $this->cubic($x);
+ $res[] = bcmul($res[1], $res[1]);
+ return $res;
+ }
+
+ private function quintic($x)
+ {
+ $res = $this->cubic($x);
+ $res[] = bcmul($res[1], $res[1]);
+ $res[] = bcmul($res[1], $res[2]);
+ return $res;
+ }
+}
+
diff --git a/classes/vendor/goat1000/svggraph/ArrayGraph.php b/classes/vendor/74x/goat1000/svggraph/ArrayGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/ArrayGraph.php
rename to classes/vendor/74x/goat1000/svggraph/ArrayGraph.php
diff --git a/classes/vendor/goat1000/svggraph/Arrow.php b/classes/vendor/74x/goat1000/svggraph/Arrow.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/Arrow.php
rename to classes/vendor/74x/goat1000/svggraph/Arrow.php
diff --git a/classes/vendor/goat1000/svggraph/Attribute.php b/classes/vendor/74x/goat1000/svggraph/Attribute.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/Attribute.php
rename to classes/vendor/74x/goat1000/svggraph/Attribute.php
diff --git a/classes/vendor/goat1000/svggraph/Average.php b/classes/vendor/74x/goat1000/svggraph/Average.php
similarity index 89%
rename from classes/vendor/goat1000/svggraph/Average.php
rename to classes/vendor/74x/goat1000/svggraph/Average.php
index cf43d0e..24a13ee 100644
--- a/classes/vendor/goat1000/svggraph/Average.php
+++ b/classes/vendor/74x/goat1000/svggraph/Average.php
@@ -1,6 +1,6 @@
getTitle($graph, $avg, $d);
- if(strlen($title) > 0)
+ if($title !== null && strlen($title) > 0)
$line[] = $title;
$cg = new ColourGroup($graph, null, 0, $d, 'average_colour');
@@ -54,13 +54,12 @@ public function __construct(&$graph, &$values, $datasets)
$line['text_colour'] = $cg->stroke();
}
- $sw = new Number($graph->getOption(['average_stroke_width', $d], 1));
- $line['stroke_width'] = $sw;
+ $line['stroke_width'] = new Number($graph->getOption(['average_stroke_width', $d], 1));
+ $line['font_size'] = Number::units($graph->getOption(['average_font_size', $d]));
$opts = ["opacity", "above", "dash", "title_align",
"title_angle", "title_opacity", "title_padding", "title_position",
- "font", "font_size", "font_adjust", "font_weight",
- "length", "length_units"];
+ "font", "font_adjust", "font_weight", "length", "length_units"];
foreach($opts as $opt) {
$g_opt = str_replace('title', 'text', $opt);
$line[$g_opt] = $graph->getOption(['average_' . $opt, $d]);
diff --git a/classes/vendor/goat1000/svggraph/Axis.php b/classes/vendor/74x/goat1000/svggraph/Axis.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/Axis.php
rename to classes/vendor/74x/goat1000/svggraph/Axis.php
diff --git a/classes/vendor/goat1000/svggraph/AxisDateTime.php b/classes/vendor/74x/goat1000/svggraph/AxisDateTime.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/AxisDateTime.php
rename to classes/vendor/74x/goat1000/svggraph/AxisDateTime.php
diff --git a/classes/vendor/goat1000/svggraph/AxisDoubleEnded.php b/classes/vendor/74x/goat1000/svggraph/AxisDoubleEnded.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/AxisDoubleEnded.php
rename to classes/vendor/74x/goat1000/svggraph/AxisDoubleEnded.php
diff --git a/classes/vendor/goat1000/svggraph/AxisFactory.php b/classes/vendor/74x/goat1000/svggraph/AxisFactory.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/AxisFactory.php
rename to classes/vendor/74x/goat1000/svggraph/AxisFactory.php
diff --git a/classes/vendor/goat1000/svggraph/AxisFixed.php b/classes/vendor/74x/goat1000/svggraph/AxisFixed.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/AxisFixed.php
rename to classes/vendor/74x/goat1000/svggraph/AxisFixed.php
diff --git a/classes/vendor/goat1000/svggraph/AxisFixedDoubleEnded.php b/classes/vendor/74x/goat1000/svggraph/AxisFixedDoubleEnded.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/AxisFixedDoubleEnded.php
rename to classes/vendor/74x/goat1000/svggraph/AxisFixedDoubleEnded.php
diff --git a/classes/vendor/goat1000/svggraph/AxisFixedTicks.php b/classes/vendor/74x/goat1000/svggraph/AxisFixedTicks.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/AxisFixedTicks.php
rename to classes/vendor/74x/goat1000/svggraph/AxisFixedTicks.php
diff --git a/classes/vendor/goat1000/svggraph/AxisFixedTicksDateTime.php b/classes/vendor/74x/goat1000/svggraph/AxisFixedTicksDateTime.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/AxisFixedTicksDateTime.php
rename to classes/vendor/74x/goat1000/svggraph/AxisFixedTicksDateTime.php
diff --git a/classes/vendor/goat1000/svggraph/AxisLog.php b/classes/vendor/74x/goat1000/svggraph/AxisLog.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/AxisLog.php
rename to classes/vendor/74x/goat1000/svggraph/AxisLog.php
diff --git a/classes/vendor/goat1000/svggraph/AxisLogTicks.php b/classes/vendor/74x/goat1000/svggraph/AxisLogTicks.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/AxisLogTicks.php
rename to classes/vendor/74x/goat1000/svggraph/AxisLogTicks.php
diff --git a/classes/vendor/goat1000/svggraph/Bar3D.php b/classes/vendor/74x/goat1000/svggraph/Bar3D.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/Bar3D.php
rename to classes/vendor/74x/goat1000/svggraph/Bar3D.php
diff --git a/classes/vendor/goat1000/svggraph/Bar3DCylinder.php b/classes/vendor/74x/goat1000/svggraph/Bar3DCylinder.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/Bar3DCylinder.php
rename to classes/vendor/74x/goat1000/svggraph/Bar3DCylinder.php
diff --git a/classes/vendor/goat1000/svggraph/Bar3DGraph.php b/classes/vendor/74x/goat1000/svggraph/Bar3DGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/Bar3DGraph.php
rename to classes/vendor/74x/goat1000/svggraph/Bar3DGraph.php
diff --git a/classes/vendor/goat1000/svggraph/BarAndLineGraph.php b/classes/vendor/74x/goat1000/svggraph/BarAndLineGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/BarAndLineGraph.php
rename to classes/vendor/74x/goat1000/svggraph/BarAndLineGraph.php
diff --git a/classes/vendor/goat1000/svggraph/BarGraph.php b/classes/vendor/74x/goat1000/svggraph/BarGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/BarGraph.php
rename to classes/vendor/74x/goat1000/svggraph/BarGraph.php
diff --git a/classes/vendor/goat1000/svggraph/BarGraphTrait.php b/classes/vendor/74x/goat1000/svggraph/BarGraphTrait.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/BarGraphTrait.php
rename to classes/vendor/74x/goat1000/svggraph/BarGraphTrait.php
diff --git a/classes/vendor/goat1000/svggraph/BestFit.php b/classes/vendor/74x/goat1000/svggraph/BestFit.php
similarity index 92%
rename from classes/vendor/goat1000/svggraph/BestFit.php
rename to classes/vendor/74x/goat1000/svggraph/BestFit.php
index c572028..c9b762a 100644
--- a/classes/vendor/goat1000/svggraph/BestFit.php
+++ b/classes/vendor/74x/goat1000/svggraph/BestFit.php
@@ -1,6 +1,6 @@
graph->getOption(['best_fit', $dataset]);
- if($type !== 'straight')
+ if($type !== 'straight' && $type !== 'curve')
return;
// range and projection
@@ -63,7 +63,10 @@ public function add($dataset, $points)
$points = $this->filterPoints($points, $r_start, $r_end);
}
- $best_fit = new BestFitLine($this->graph, $points);
+ $class = '\\Goat1000\\SVGGraph\\' . ($type === 'straight' ? 'BestFitLine' : 'BestFitCurve');
+ $subtypes = $this->graph->getOption(['best_fit_type', $dataset]);
+
+ $best_fit = new $class($this->graph, $points, $subtypes);
$best_fit->calculate($this->bbox, $r_start, $r_end, $p_start, $p_end);
if($this->graph->getOption(['best_fit_above', $dataset]))
$this->lines_above[$dataset] = $best_fit;
@@ -162,6 +165,7 @@ protected function getLinePath($dataset, $line_path, $proj_path)
$dash = $this->graph->getOption(['best_fit_dash', $dataset]);
$opacity = $this->graph->getOption(['best_fit_opacity', $dataset]);
$above = $this->graph->getOption(['best_fit_above', $dataset]);
+ $type = $this->graph->getOption(['best_fit', $dataset]);
$path = [
'd' => $line_path,
'stroke' => $colour->isNone() ? '#000' : $colour,
@@ -172,6 +176,8 @@ protected function getLinePath($dataset, $line_path, $proj_path)
$path['stroke-dasharray'] = $dash;
if($opacity != 1)
$path['opacity'] = $opacity;
+ if($type != 'straight')
+ $path['fill'] = 'none'; // don't fill curves
$line = $this->graph->element('path', $path);
if($proj_path->isEmpty())
diff --git a/classes/vendor/74x/goat1000/svggraph/BestFitCurve.php b/classes/vendor/74x/goat1000/svggraph/BestFitCurve.php
new file mode 100644
index 0000000..8e4005a
--- /dev/null
+++ b/classes/vendor/74x/goat1000/svggraph/BestFitCurve.php
@@ -0,0 +1,245 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * Class for calculating a curved best-fit line
+ */
+class BestFitCurve {
+
+ protected $graph;
+ protected $points;
+ protected $line;
+ protected $projection;
+ protected $types;
+
+ public function __construct(&$graph, $points, $types = null)
+ {
+ $this->graph =& $graph;
+ $this->line = new PathData;
+ $this->projection = new PathData;
+ $this->points = $points;
+ $this->types = $types === null || is_array($types) ? $types : [$types];
+ }
+
+ /**
+ * Calculates the line and projection
+ */
+ public function calculate(BoundingBox $area, $limit_start, $limit_end,
+ $project_start, $project_end)
+ {
+ // can't draw a line through fewer than 2 points
+ $count = count($this->points);
+ if($count < 2)
+ return false;
+
+ $old_scale = bcscale(50);
+ $b = [];
+ $y = new Matrix($count, 1);
+ foreach($this->points as $p)
+ $b[] = $p->y;
+ $y->load($b);
+
+ $supported_types = ['straight', 'quadratic', 'cubic', 'quartic', 'quintic'];
+
+ // choose which functions to fit
+ if($this->types !== null) {
+ $types = [];
+ foreach($this->types as $type) {
+ if(!in_array($type, $supported_types))
+ throw new \Exception("Unknown curve type '{$type}'");
+ $types[] = $type;
+ }
+ } else {
+ $types = ['quintic'];
+ switch($count)
+ {
+ case 2 : $types = ['straight'];
+ break;
+ case 3 : $types = ['quadratic'];
+ break;
+ case 4 : $types = ['cubic'];
+ break;
+ case 5 : $types = ['quartic'];
+ }
+ }
+
+ // fit the functions, measure the error
+ $results = [];
+ $errors = [];
+ foreach($types as $t) {
+ $v = $this->vandermonde($t);
+ $result = $this->solve($v, $y);
+ if($result !== null) {
+ $errors[$t] = $this->error($v, $result);
+ $results[$t] = $result;
+ }
+ }
+
+ if(!empty($errors)) {
+
+ // sort by error, best first
+ uasort($errors, 'bccomp');
+ $best = null;
+ foreach($errors as $k => $v) {
+ $best = $k;
+ break;
+ }
+
+ $r = $results[$best];
+ $c = $r->asArray();
+
+ // plot the function
+ $fn = new Algebraic($best);
+ $fn->setCoefficients($c);
+ $this->buildPaths($fn, $area, $limit_start, $limit_end,
+ $project_start, $project_end);
+ }
+ bcscale($old_scale);
+ }
+
+ /**
+ * Calculates the error
+ */
+ protected function error(Matrix $v, Matrix $r)
+ {
+ $old_scale = bcscale(50);
+ $tr = $r->transpose();
+ $vr = $v->multiply($tr);
+ $i = 0;
+ $err = "0";
+ foreach($this->points as $p) {
+ $diff = bcsub($vr($i, 0), $p->y);
+ if(bccomp($diff, "0") == -1)
+ $err = bcsub($err, $diff);
+ else
+ $err = bcadd($err, $diff);
+ ++$i;
+ }
+ bcscale($old_scale);
+ return $err;
+ }
+
+ /**
+ * Solves the normal equation
+ */
+ protected function solve(Matrix $v, Matrix $y)
+ {
+ $v_t = $v->transpose();
+ $v_t_v = $v_t->multiply($v);
+ $v_t_y = $v_t->multiply($y);
+ return $v_t_v->gaussian_solve($v_t_y);
+ }
+
+ /**
+ * Returns the Vandermonde matrix for the curve type
+ */
+ protected function vandermonde($type)
+ {
+ $old_scale = bcscale(50);
+
+ // find size of matrix
+ $a = new Algebraic($type);
+ $test = $a->vandermonde(1);
+ $m = count($this->points);
+ $n = count($test) + 1;
+ $v = new Matrix($m, $n);
+
+ $i = 0;
+ foreach($this->points as $p)
+ {
+ $v($i, 0, 1);
+ $bcx = sprintf("%20.20F", $p->x);
+ $cols = $a->vandermonde($bcx);
+ foreach($cols as $k => $value)
+ $v($i, $k + 1, $value);
+ ++$i;
+ }
+ bcscale($old_scale);
+ return $v;
+ }
+
+ /**
+ * Builds the line and projection paths.
+ * For vertical lines, $slope = null and $y_int = $x
+ */
+ protected function buildPaths(&$fn, $area, $limit_start, $limit_end,
+ $project_start, $project_end)
+ {
+
+ // initialize min and max points of line
+ $x_min = $limit_start === null ? 0 : max($limit_start, 0);
+ $x_max = $limit_end === null ? $area->width() : min($limit_end, $area->width());
+ $y_min = 0;
+ $y_max = $area->height();
+ $line = new PathData;
+ $projection = new PathData;
+
+ $step = 1;
+ if($project_start)
+ $this->buildPath($projection, $fn, 0, $x_min, $y_min, $y_max, $step, $area);
+ $this->buildPath($line, $fn, $x_min, $x_max, $y_min, $y_max, $step, $area);
+ if($project_end)
+ $this->buildPath($projection, $fn, $x_max, $area->width(), $y_min, $y_max, $step, $area);
+
+ $this->projection = $projection;
+ $this->line = $line;
+ return true;
+ }
+
+ /**
+ * Builds a single path section between $x1 and $x2
+ */
+ private function buildPath(&$path, &$fn, $x1, $x2, $y_min, $y_max, $step, $area)
+ {
+ $cmd = 'M';
+ for($x = $x1; $x <= $x2; $x += $step) {
+ $y = $fn($x);
+ if($y < $y_min || $y > $y_max) {
+ $cmd = 'M';
+ continue;
+ }
+ $path->add($cmd, $area->x1 + $x, $area->y2 - $y);
+ switch($cmd) {
+ case 'M' : $cmd = 'L'; break;
+ case 'L' : $cmd = ''; break;
+ }
+ }
+ }
+
+ /**
+ * Returns the best-fit line as PathData
+ */
+ public function getLine()
+ {
+ return $this->line;
+ }
+
+ /**
+ * Returns the projection line(s) as PathData
+ */
+ public function getProjection()
+ {
+ return $this->projection;
+ }
+}
+
diff --git a/classes/vendor/goat1000/svggraph/BestFitLine.php b/classes/vendor/74x/goat1000/svggraph/BestFitLine.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/BestFitLine.php
rename to classes/vendor/74x/goat1000/svggraph/BestFitLine.php
diff --git a/classes/vendor/goat1000/svggraph/BoundingBox.php b/classes/vendor/74x/goat1000/svggraph/BoundingBox.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/BoundingBox.php
rename to classes/vendor/74x/goat1000/svggraph/BoundingBox.php
diff --git a/classes/vendor/goat1000/svggraph/BoxAndWhiskerGraph.php b/classes/vendor/74x/goat1000/svggraph/BoxAndWhiskerGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/BoxAndWhiskerGraph.php
rename to classes/vendor/74x/goat1000/svggraph/BoxAndWhiskerGraph.php
diff --git a/classes/vendor/goat1000/svggraph/BubbleGraph.php b/classes/vendor/74x/goat1000/svggraph/BubbleGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/BubbleGraph.php
rename to classes/vendor/74x/goat1000/svggraph/BubbleGraph.php
diff --git a/classes/vendor/goat1000/svggraph/CHANGES.txt b/classes/vendor/74x/goat1000/svggraph/CHANGES.txt
similarity index 99%
rename from classes/vendor/goat1000/svggraph/CHANGES.txt
rename to classes/vendor/74x/goat1000/svggraph/CHANGES.txt
index 397e460..3fc24c1 100644
--- a/classes/vendor/goat1000/svggraph/CHANGES.txt
+++ b/classes/vendor/74x/goat1000/svggraph/CHANGES.txt
@@ -1,3 +1,8 @@
+Version 3.20 (24/04/2023)
+------------
+- Added support for curved best-fit lines.
+- Added support for CSS units in font sizes and line spacing.
+
Version 3.19 (10/01/2023)
------------
- Added ParetoChart.
diff --git a/classes/vendor/goat1000/svggraph/COPYING b/classes/vendor/74x/goat1000/svggraph/COPYING
similarity index 100%
rename from classes/vendor/goat1000/svggraph/COPYING
rename to classes/vendor/74x/goat1000/svggraph/COPYING
diff --git a/classes/vendor/goat1000/svggraph/COPYING.LESSER b/classes/vendor/74x/goat1000/svggraph/COPYING.LESSER
similarity index 100%
rename from classes/vendor/goat1000/svggraph/COPYING.LESSER
rename to classes/vendor/74x/goat1000/svggraph/COPYING.LESSER
diff --git a/classes/vendor/goat1000/svggraph/CandlestickGraph.php b/classes/vendor/74x/goat1000/svggraph/CandlestickGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/CandlestickGraph.php
rename to classes/vendor/74x/goat1000/svggraph/CandlestickGraph.php
diff --git a/classes/vendor/goat1000/svggraph/Circle.php b/classes/vendor/74x/goat1000/svggraph/Circle.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/Circle.php
rename to classes/vendor/74x/goat1000/svggraph/Circle.php
diff --git a/classes/vendor/goat1000/svggraph/Colour.php b/classes/vendor/74x/goat1000/svggraph/Colour.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/Colour.php
rename to classes/vendor/74x/goat1000/svggraph/Colour.php
diff --git a/classes/vendor/goat1000/svggraph/ColourArray.php b/classes/vendor/74x/goat1000/svggraph/ColourArray.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/ColourArray.php
rename to classes/vendor/74x/goat1000/svggraph/ColourArray.php
diff --git a/classes/vendor/goat1000/svggraph/ColourFilter.php b/classes/vendor/74x/goat1000/svggraph/ColourFilter.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/ColourFilter.php
rename to classes/vendor/74x/goat1000/svggraph/ColourFilter.php
diff --git a/classes/vendor/goat1000/svggraph/ColourGroup.php b/classes/vendor/74x/goat1000/svggraph/ColourGroup.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/ColourGroup.php
rename to classes/vendor/74x/goat1000/svggraph/ColourGroup.php
diff --git a/classes/vendor/goat1000/svggraph/ColourRange.php b/classes/vendor/74x/goat1000/svggraph/ColourRange.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/ColourRange.php
rename to classes/vendor/74x/goat1000/svggraph/ColourRange.php
diff --git a/classes/vendor/goat1000/svggraph/ColourRangeHSL.php b/classes/vendor/74x/goat1000/svggraph/ColourRangeHSL.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/ColourRangeHSL.php
rename to classes/vendor/74x/goat1000/svggraph/ColourRangeHSL.php
diff --git a/classes/vendor/goat1000/svggraph/ColourRangeRGB.php b/classes/vendor/74x/goat1000/svggraph/ColourRangeRGB.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/ColourRangeRGB.php
rename to classes/vendor/74x/goat1000/svggraph/ColourRangeRGB.php
diff --git a/classes/vendor/goat1000/svggraph/Colours.php b/classes/vendor/74x/goat1000/svggraph/Colours.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/Colours.php
rename to classes/vendor/74x/goat1000/svggraph/Colours.php
diff --git a/classes/vendor/goat1000/svggraph/ContextMenu.php b/classes/vendor/74x/goat1000/svggraph/ContextMenu.php
similarity index 96%
rename from classes/vendor/goat1000/svggraph/ContextMenu.php
rename to classes/vendor/74x/goat1000/svggraph/ContextMenu.php
index 955492a..9d08d4c 100644
--- a/classes/vendor/goat1000/svggraph/ContextMenu.php
+++ b/classes/vendor/74x/goat1000/svggraph/ContextMenu.php
@@ -1,6 +1,6 @@
js->addInitFunction('contextMenuInit');
$opts = ['link_target', 'link_underline', 'stroke_width', 'round', 'font',
- 'font_size', 'font_weight', 'document_menu', 'spacing', 'min_width',
+ 'font_weight', 'document_menu', 'spacing', 'min_width',
'shadow_opacity', 'mouseleave'];
$colours = ['colour', 'link_colour', 'link_hover_colour', 'back_colour'];
$vars = [];
foreach($opts as $opt)
$vars[$opt] = $this->graph->getOption('context_' . $opt);
+ $vars['font_size'] = Number::units($this->graph->getOption('context_font_size'));
foreach($colours as $opt)
$vars[$opt] = new Colour($this->graph, $this->graph->getOption('context_' . $opt));
diff --git a/classes/vendor/goat1000/svggraph/Coords.php b/classes/vendor/74x/goat1000/svggraph/Coords.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/Coords.php
rename to classes/vendor/74x/goat1000/svggraph/Coords.php
diff --git a/classes/vendor/goat1000/svggraph/CrossHairs.php b/classes/vendor/74x/goat1000/svggraph/CrossHairs.php
similarity index 99%
rename from classes/vendor/goat1000/svggraph/CrossHairs.php
rename to classes/vendor/74x/goat1000/svggraph/CrossHairs.php
index 7f68c25..8caa637 100644
--- a/classes/vendor/goat1000/svggraph/CrossHairs.php
+++ b/classes/vendor/74x/goat1000/svggraph/CrossHairs.php
@@ -121,6 +121,7 @@ public function getCrossHairs()
foreach($text_options as $opt)
$t_opt[$opt] = $this->graph->getOption('crosshairs_text_' . $opt);
+
// text group for grid details
$text_group = ['id' => $this->graph->newId(), 'visibility' => 'hidden'];
$text_rect = [
@@ -133,7 +134,7 @@ public function getCrossHairs()
$text_rect['stroke-width'] = $t_opt['stroke_width'];
$text_rect['stroke'] = $t_opt['colour'];
}
- $font_size = max(3, (int)$t_opt['font_size']);
+ $font_size = max(3, Number::units($t_opt['font_size']));
$text_element = [
'x' => 0, 'y' => $font_size,
'font-family' => $t_opt['font'],
diff --git a/classes/vendor/goat1000/svggraph/CylinderGraph.php b/classes/vendor/74x/goat1000/svggraph/CylinderGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/CylinderGraph.php
rename to classes/vendor/74x/goat1000/svggraph/CylinderGraph.php
diff --git a/classes/vendor/goat1000/svggraph/Data.php b/classes/vendor/74x/goat1000/svggraph/Data.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/Data.php
rename to classes/vendor/74x/goat1000/svggraph/Data.php
diff --git a/classes/vendor/goat1000/svggraph/DataItem.php b/classes/vendor/74x/goat1000/svggraph/DataItem.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/DataItem.php
rename to classes/vendor/74x/goat1000/svggraph/DataItem.php
diff --git a/classes/vendor/goat1000/svggraph/DataIterator.php b/classes/vendor/74x/goat1000/svggraph/DataIterator.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/DataIterator.php
rename to classes/vendor/74x/goat1000/svggraph/DataIterator.php
diff --git a/classes/vendor/goat1000/svggraph/DataLabels.php b/classes/vendor/74x/goat1000/svggraph/DataLabels.php
similarity index 99%
rename from classes/vendor/goat1000/svggraph/DataLabels.php
rename to classes/vendor/74x/goat1000/svggraph/DataLabels.php
index a778daa..a78bf60 100644
--- a/classes/vendor/goat1000/svggraph/DataLabels.php
+++ b/classes/vendor/74x/goat1000/svggraph/DataLabels.php
@@ -1,6 +1,6 @@
getOption(
['axis_text_colour_' . $o, $axis_no], 'axis_text_colour',
['@', $styles['colour']]));
- $styles['t_line_spacing'] = $get_axis_option('axis_text_line_spacing');
+ $styles['t_line_spacing'] = Number::units($get_axis_option('axis_text_line_spacing'));
if($styles['t_line_spacing'] === null || $styles['t_line_spacing'] < 1)
$styles['t_line_spacing'] = $styles['t_font_size'];
$styles['t_back_colour'] = null;
@@ -202,9 +202,9 @@ public function __construct(&$graph, &$axis, $axis_no, $orientation, $type,
$styles['l_font'] = $graph->getOption(
['label_font_' . $o, $axis_no], 'label_font',
['axis_font_' . $o, $axis_no], 'axis_font');
- $styles['l_font_size'] = $graph->getOption(
+ $styles['l_font_size'] = Number::units($graph->getOption(
['label_font_size_' . $o, $axis_no], 'label_font_size',
- ['axis_font_size_' . $o, $axis_no], 'axis_font_size');
+ ['axis_font_size_' . $o, $axis_no], 'axis_font_size'));
$styles['l_font_weight'] = $graph->getOption(
['label_font_weight_' . $o, $axis_no], 'label_font_weight');
$styles['l_colour'] = new Colour($graph, $graph->getOption(
@@ -213,7 +213,7 @@ public function __construct(&$graph, &$axis, $axis_no, $orientation, $type,
['@', $styles['colour']]));
$styles['l_space'] = $graph->getOption('label_space');
$styles['l_pos'] = $get_axis_option('axis_label_position');
- $styles['l_line_spacing'] = $get_axis_option('label_line_spacing');
+ $styles['l_line_spacing'] = Number::units($get_axis_option('label_line_spacing'));
if($styles['l_line_spacing'] === null || $styles['l_line_spacing'] < 1)
$styles['l_line_spacing'] = $styles['l_font_size'];
}
diff --git a/classes/vendor/goat1000/svggraph/DisplayAxisLevels.php b/classes/vendor/74x/goat1000/svggraph/DisplayAxisLevels.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/DisplayAxisLevels.php
rename to classes/vendor/74x/goat1000/svggraph/DisplayAxisLevels.php
diff --git a/classes/vendor/goat1000/svggraph/DisplayAxisRadar.php b/classes/vendor/74x/goat1000/svggraph/DisplayAxisRadar.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/DisplayAxisRadar.php
rename to classes/vendor/74x/goat1000/svggraph/DisplayAxisRadar.php
diff --git a/classes/vendor/goat1000/svggraph/DisplayAxisRotated.php b/classes/vendor/74x/goat1000/svggraph/DisplayAxisRotated.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/DisplayAxisRotated.php
rename to classes/vendor/74x/goat1000/svggraph/DisplayAxisRotated.php
diff --git a/classes/vendor/goat1000/svggraph/Donut3DGraph.php b/classes/vendor/74x/goat1000/svggraph/Donut3DGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/Donut3DGraph.php
rename to classes/vendor/74x/goat1000/svggraph/Donut3DGraph.php
diff --git a/classes/vendor/goat1000/svggraph/DonutGraph.php b/classes/vendor/74x/goat1000/svggraph/DonutGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/DonutGraph.php
rename to classes/vendor/74x/goat1000/svggraph/DonutGraph.php
diff --git a/classes/vendor/goat1000/svggraph/DonutGraphTrait.php b/classes/vendor/74x/goat1000/svggraph/DonutGraphTrait.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/DonutGraphTrait.php
rename to classes/vendor/74x/goat1000/svggraph/DonutGraphTrait.php
diff --git a/classes/vendor/goat1000/svggraph/DonutSliceEdge.php b/classes/vendor/74x/goat1000/svggraph/DonutSliceEdge.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/DonutSliceEdge.php
rename to classes/vendor/74x/goat1000/svggraph/DonutSliceEdge.php
diff --git a/classes/vendor/goat1000/svggraph/Ellipse.php b/classes/vendor/74x/goat1000/svggraph/Ellipse.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/Ellipse.php
rename to classes/vendor/74x/goat1000/svggraph/Ellipse.php
diff --git a/classes/vendor/goat1000/svggraph/EmptyGraph.php b/classes/vendor/74x/goat1000/svggraph/EmptyGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/EmptyGraph.php
rename to classes/vendor/74x/goat1000/svggraph/EmptyGraph.php
diff --git a/classes/vendor/goat1000/svggraph/ExplodedDonut3DGraph.php b/classes/vendor/74x/goat1000/svggraph/ExplodedDonut3DGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/ExplodedDonut3DGraph.php
rename to classes/vendor/74x/goat1000/svggraph/ExplodedDonut3DGraph.php
diff --git a/classes/vendor/goat1000/svggraph/ExplodedDonutGraph.php b/classes/vendor/74x/goat1000/svggraph/ExplodedDonutGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/ExplodedDonutGraph.php
rename to classes/vendor/74x/goat1000/svggraph/ExplodedDonutGraph.php
diff --git a/classes/vendor/goat1000/svggraph/ExplodedPie3DGraph.php b/classes/vendor/74x/goat1000/svggraph/ExplodedPie3DGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/ExplodedPie3DGraph.php
rename to classes/vendor/74x/goat1000/svggraph/ExplodedPie3DGraph.php
diff --git a/classes/vendor/goat1000/svggraph/ExplodedPieGraph.php b/classes/vendor/74x/goat1000/svggraph/ExplodedPieGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/ExplodedPieGraph.php
rename to classes/vendor/74x/goat1000/svggraph/ExplodedPieGraph.php
diff --git a/classes/vendor/goat1000/svggraph/ExplodedPieGraphTrait.php b/classes/vendor/74x/goat1000/svggraph/ExplodedPieGraphTrait.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/ExplodedPieGraphTrait.php
rename to classes/vendor/74x/goat1000/svggraph/ExplodedPieGraphTrait.php
diff --git a/classes/vendor/goat1000/svggraph/ExplodedSemiDonut3DGraph.php b/classes/vendor/74x/goat1000/svggraph/ExplodedSemiDonut3DGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/ExplodedSemiDonut3DGraph.php
rename to classes/vendor/74x/goat1000/svggraph/ExplodedSemiDonut3DGraph.php
diff --git a/classes/vendor/goat1000/svggraph/ExplodedSemiDonutGraph.php b/classes/vendor/74x/goat1000/svggraph/ExplodedSemiDonutGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/ExplodedSemiDonutGraph.php
rename to classes/vendor/74x/goat1000/svggraph/ExplodedSemiDonutGraph.php
diff --git a/classes/vendor/goat1000/svggraph/FieldSort.php b/classes/vendor/74x/goat1000/svggraph/FieldSort.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/FieldSort.php
rename to classes/vendor/74x/goat1000/svggraph/FieldSort.php
diff --git a/classes/vendor/goat1000/svggraph/FigureShape.php b/classes/vendor/74x/goat1000/svggraph/FigureShape.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/FigureShape.php
rename to classes/vendor/74x/goat1000/svggraph/FigureShape.php
diff --git a/classes/vendor/goat1000/svggraph/Figures.php b/classes/vendor/74x/goat1000/svggraph/Figures.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/Figures.php
rename to classes/vendor/74x/goat1000/svggraph/Figures.php
diff --git a/classes/vendor/goat1000/svggraph/FilterList.php b/classes/vendor/74x/goat1000/svggraph/FilterList.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/FilterList.php
rename to classes/vendor/74x/goat1000/svggraph/FilterList.php
diff --git a/classes/vendor/goat1000/svggraph/FloatingBarGraph.php b/classes/vendor/74x/goat1000/svggraph/FloatingBarGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/FloatingBarGraph.php
rename to classes/vendor/74x/goat1000/svggraph/FloatingBarGraph.php
diff --git a/classes/vendor/goat1000/svggraph/FloatingBarTrait.php b/classes/vendor/74x/goat1000/svggraph/FloatingBarTrait.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/FloatingBarTrait.php
rename to classes/vendor/74x/goat1000/svggraph/FloatingBarTrait.php
diff --git a/classes/vendor/goat1000/svggraph/GanttArrow.php b/classes/vendor/74x/goat1000/svggraph/GanttArrow.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/GanttArrow.php
rename to classes/vendor/74x/goat1000/svggraph/GanttArrow.php
diff --git a/classes/vendor/goat1000/svggraph/GanttChart.php b/classes/vendor/74x/goat1000/svggraph/GanttChart.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/GanttChart.php
rename to classes/vendor/74x/goat1000/svggraph/GanttChart.php
diff --git a/classes/vendor/goat1000/svggraph/GradientList.php b/classes/vendor/74x/goat1000/svggraph/GradientList.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/GradientList.php
rename to classes/vendor/74x/goat1000/svggraph/GradientList.php
diff --git a/classes/vendor/goat1000/svggraph/Graph.php b/classes/vendor/74x/goat1000/svggraph/Graph.php
similarity index 99%
rename from classes/vendor/goat1000/svggraph/Graph.php
rename to classes/vendor/74x/goat1000/svggraph/Graph.php
index 89f8bee..fb9b530 100644
--- a/classes/vendor/goat1000/svggraph/Graph.php
+++ b/classes/vendor/74x/goat1000/svggraph/Graph.php
@@ -557,12 +557,12 @@ protected function getTitle()
if($svg_text->strlen($info['title']) <= 0)
return $info;
- $info['font_size'] = $this->getOption('graph_title_font_size');
+ $info['font_size'] = Number::units($this->getOption('graph_title_font_size'));
$info['weight'] = $this->getOption('graph_title_font_weight');
$info['colour'] = $this->getOption('graph_title_colour');
$info['pos'] = $this->getOption('graph_title_position');
$info['space'] = $this->getOption('graph_title_space');
- $info['line_spacing'] = $this->getOption('graph_title_line_spacing');
+ $info['line_spacing'] = Number::units($this->getOption('graph_title_line_spacing'));
if($info['line_spacing'] === null || $info['line_spacing'] < 1)
$info['line_spacing'] = $info['font_size'];
@@ -579,15 +579,15 @@ protected function getTitle()
$svg_subtext = new Text($this, $info['sfont']);
if($svg_subtext->strlen($info['stitle']) > 0) {
- $info['sfont_size'] = $this->getOption('graph_subtitle_font_size',
- 'graph_title_font_size');
+ $info['sfont_size'] = Number::units($this->getOption('graph_subtitle_font_size',
+ 'graph_title_font_size'));
$info['sweight'] = $this->getOption('graph_subtitle_font_weight',
'graph_title_font_weight');
$info['scolour'] = $this->getOption('graph_subtitle_colour',
'graph_title_colour');
$info['sspace'] = $this->getOption('graph_subtitle_space',
'graph_title_space');
- $info['sline_spacing'] = $this->getOption('graph_subtitle_line_spacing');
+ $info['sline_spacing'] = Number::units($this->getOption('graph_subtitle_line_spacing'));
if($info['sline_spacing'] === null || $info['sline_spacing'] < 1)
$info['sline_spacing'] = $info['sfont_size'];
diff --git a/classes/vendor/goat1000/svggraph/GridGraph.php b/classes/vendor/74x/goat1000/svggraph/GridGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/GridGraph.php
rename to classes/vendor/74x/goat1000/svggraph/GridGraph.php
diff --git a/classes/vendor/goat1000/svggraph/GridPoint.php b/classes/vendor/74x/goat1000/svggraph/GridPoint.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/GridPoint.php
rename to classes/vendor/74x/goat1000/svggraph/GridPoint.php
diff --git a/classes/vendor/goat1000/svggraph/Grouped3DGraphTrait.php b/classes/vendor/74x/goat1000/svggraph/Grouped3DGraphTrait.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/Grouped3DGraphTrait.php
rename to classes/vendor/74x/goat1000/svggraph/Grouped3DGraphTrait.php
diff --git a/classes/vendor/goat1000/svggraph/GroupedBar3DGraph.php b/classes/vendor/74x/goat1000/svggraph/GroupedBar3DGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/GroupedBar3DGraph.php
rename to classes/vendor/74x/goat1000/svggraph/GroupedBar3DGraph.php
diff --git a/classes/vendor/goat1000/svggraph/GroupedBarGraph.php b/classes/vendor/74x/goat1000/svggraph/GroupedBarGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/GroupedBarGraph.php
rename to classes/vendor/74x/goat1000/svggraph/GroupedBarGraph.php
diff --git a/classes/vendor/goat1000/svggraph/GroupedBarTrait.php b/classes/vendor/74x/goat1000/svggraph/GroupedBarTrait.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/GroupedBarTrait.php
rename to classes/vendor/74x/goat1000/svggraph/GroupedBarTrait.php
diff --git a/classes/vendor/goat1000/svggraph/GroupedCylinderGraph.php b/classes/vendor/74x/goat1000/svggraph/GroupedCylinderGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/GroupedCylinderGraph.php
rename to classes/vendor/74x/goat1000/svggraph/GroupedCylinderGraph.php
diff --git a/classes/vendor/goat1000/svggraph/Guidelines.php b/classes/vendor/74x/goat1000/svggraph/Guidelines.php
similarity index 95%
rename from classes/vendor/goat1000/svggraph/Guidelines.php
rename to classes/vendor/74x/goat1000/svggraph/Guidelines.php
index 86330ec..c580b83 100644
--- a/classes/vendor/goat1000/svggraph/Guidelines.php
+++ b/classes/vendor/74x/goat1000/svggraph/Guidelines.php
@@ -1,6 +1,6 @@
guidelines = [];
// set up options
- $opts = ['above', 'dash', 'font', 'font_adjust', 'font_size',
- 'font_weight', 'length', 'length_units', 'opacity', 'stroke_width',
- 'text_align', 'text_angle', 'text_padding', 'text_position', 'line_spacing'];
+ $opts = ['above', 'dash', 'font', 'font_adjust', 'font_weight',
+ 'length', 'length_units', 'opacity', 'stroke_width',
+ 'text_align', 'text_angle', 'text_padding', 'text_position' ];
foreach($opts as $opt)
$this->{$opt} = $graph->getOption('guideline_' . $opt);
@@ -80,6 +80,8 @@ public function __construct(&$graph, $flip_axes, $assoc, $datetime)
$graph->getOption('guideline_text_colour', 'guideline_colour'));
$this->text_opacity = $graph->getOption('guideline_text_opacity',
'guideline_opacity');
+ $this->font_size = Number::units($graph->getOption('guideline_font_size'));
+ $this->line_spacing = Number::units($graph->getOption('guideline_line_spacing'));
$lines = $this->normalize($lines);
foreach($lines as $line)
@@ -154,7 +156,6 @@ protected function calculate($g)
$text_opts = [
'opacity' => 'opacity',
'font' => 'font-family',
- 'font_size' => 'font-size',
'font_weight' => 'font-weight',
'text_opacity' => 'opacity', // overrides line opacity
@@ -164,7 +165,6 @@ protected function calculate($g)
'text_padding' => 'text_padding',
'text_angle' => 'text_angle',
'text_align' => 'text_align',
- 'line_spacing' => 'line_spacing',
];
// handle colours first
@@ -177,6 +177,12 @@ protected function calculate($g)
$topts['fill'] = new Colour($this->graph, $g['text_colour']);
}
+ // font size and line spacing
+ if(isset($g['font_size']))
+ $topts['font-size'] = Number::units($g['font_size']);
+ if(isset($g['line_spacing']))
+ $topts['line_spacing'] = Number::units($g['line_spacing']);
+
// copy other options to line or text array
foreach($line_opts as $okey => $opt)
if(isset($g[$okey]))
diff --git a/classes/vendor/goat1000/svggraph/Histogram.php b/classes/vendor/74x/goat1000/svggraph/Histogram.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/Histogram.php
rename to classes/vendor/74x/goat1000/svggraph/Histogram.php
diff --git a/classes/vendor/goat1000/svggraph/HistogramAverage.php b/classes/vendor/74x/goat1000/svggraph/HistogramAverage.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/HistogramAverage.php
rename to classes/vendor/74x/goat1000/svggraph/HistogramAverage.php
diff --git a/classes/vendor/goat1000/svggraph/HorizontalBar3DGraph.php b/classes/vendor/74x/goat1000/svggraph/HorizontalBar3DGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/HorizontalBar3DGraph.php
rename to classes/vendor/74x/goat1000/svggraph/HorizontalBar3DGraph.php
diff --git a/classes/vendor/goat1000/svggraph/HorizontalBarGraph.php b/classes/vendor/74x/goat1000/svggraph/HorizontalBarGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/HorizontalBarGraph.php
rename to classes/vendor/74x/goat1000/svggraph/HorizontalBarGraph.php
diff --git a/classes/vendor/goat1000/svggraph/HorizontalBarGraphTrait.php b/classes/vendor/74x/goat1000/svggraph/HorizontalBarGraphTrait.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/HorizontalBarGraphTrait.php
rename to classes/vendor/74x/goat1000/svggraph/HorizontalBarGraphTrait.php
diff --git a/classes/vendor/goat1000/svggraph/HorizontalFloatingBarGraph.php b/classes/vendor/74x/goat1000/svggraph/HorizontalFloatingBarGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/HorizontalFloatingBarGraph.php
rename to classes/vendor/74x/goat1000/svggraph/HorizontalFloatingBarGraph.php
diff --git a/classes/vendor/goat1000/svggraph/HorizontalGridGraph.php b/classes/vendor/74x/goat1000/svggraph/HorizontalGridGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/HorizontalGridGraph.php
rename to classes/vendor/74x/goat1000/svggraph/HorizontalGridGraph.php
diff --git a/classes/vendor/goat1000/svggraph/HorizontalGroupedBar3DGraph.php b/classes/vendor/74x/goat1000/svggraph/HorizontalGroupedBar3DGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/HorizontalGroupedBar3DGraph.php
rename to classes/vendor/74x/goat1000/svggraph/HorizontalGroupedBar3DGraph.php
diff --git a/classes/vendor/goat1000/svggraph/HorizontalGroupedBarGraph.php b/classes/vendor/74x/goat1000/svggraph/HorizontalGroupedBarGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/HorizontalGroupedBarGraph.php
rename to classes/vendor/74x/goat1000/svggraph/HorizontalGroupedBarGraph.php
diff --git a/classes/vendor/goat1000/svggraph/HorizontalStackedBar3DGraph.php b/classes/vendor/74x/goat1000/svggraph/HorizontalStackedBar3DGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/HorizontalStackedBar3DGraph.php
rename to classes/vendor/74x/goat1000/svggraph/HorizontalStackedBar3DGraph.php
diff --git a/classes/vendor/goat1000/svggraph/HorizontalStackedBarGraph.php b/classes/vendor/74x/goat1000/svggraph/HorizontalStackedBarGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/HorizontalStackedBarGraph.php
rename to classes/vendor/74x/goat1000/svggraph/HorizontalStackedBarGraph.php
diff --git a/classes/vendor/goat1000/svggraph/HorizontalThreeDGraph.php b/classes/vendor/74x/goat1000/svggraph/HorizontalThreeDGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/HorizontalThreeDGraph.php
rename to classes/vendor/74x/goat1000/svggraph/HorizontalThreeDGraph.php
diff --git a/classes/vendor/goat1000/svggraph/Image.php b/classes/vendor/74x/goat1000/svggraph/Image.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/Image.php
rename to classes/vendor/74x/goat1000/svggraph/Image.php
diff --git a/classes/vendor/goat1000/svggraph/Javascript.php b/classes/vendor/74x/goat1000/svggraph/Javascript.php
similarity index 98%
rename from classes/vendor/goat1000/svggraph/Javascript.php
rename to classes/vendor/74x/goat1000/svggraph/Javascript.php
index 6af2a01..9b80ad8 100644
--- a/classes/vendor/goat1000/svggraph/Javascript.php
+++ b/classes/vendor/74x/goat1000/svggraph/Javascript.php
@@ -1,6 +1,6 @@
addFuncs('getE', 'setattr', 'newel', 'newtext');
- $opts = ['padding', 'colour', 'font', 'font_size', 'font_weight',
- 'line_spacing', 'align'];
+ $opts = ['padding', 'colour', 'font', 'font_weight', 'align'];
$vars = [];
foreach($opts as $opt)
$vars[$opt] = $this->graph->getOption('tooltip_' . $opt);
+ $vars['font_size'] = Number::units($this->graph->getOption('tooltip_font_size'));
+ $vars['line_spacing'] = Number::units($this->graph->getOption('tooltip_line_spacing'));
$vars['colour'] = new Colour($this->graph, $vars['colour'], false, false);
$vars['ttoffset'] = $vars['font_size'] + $vars['padding'];
if($vars['line_spacing'] === null || $vars['line_spacing'] < 1)
diff --git a/classes/vendor/goat1000/svggraph/LICENSE.txt b/classes/vendor/74x/goat1000/svggraph/LICENSE.txt
similarity index 100%
rename from classes/vendor/goat1000/svggraph/LICENSE.txt
rename to classes/vendor/74x/goat1000/svggraph/LICENSE.txt
diff --git a/classes/vendor/goat1000/svggraph/Legend.php b/classes/vendor/74x/goat1000/svggraph/Legend.php
similarity index 97%
rename from classes/vendor/goat1000/svggraph/Legend.php
rename to classes/vendor/74x/goat1000/svggraph/Legend.php
index 91dea32..b5799df 100644
--- a/classes/vendor/goat1000/svggraph/Legend.php
+++ b/classes/vendor/74x/goat1000/svggraph/Legend.php
@@ -67,7 +67,7 @@ public function __construct(&$graph)
// copy options to class
$opts = ['autohide', 'autohide_opacity', 'back_colour', 'colour', 'columns',
'draggable', 'entries', 'entry_height', 'entry_width', 'font',
- 'font_adjust', 'font_size', 'font_weight', 'position', 'round',
+ 'font_adjust', 'font_weight', 'position', 'round',
'shadow_opacity', 'show_empty', 'stroke_colour', 'stroke_width',
'text_side', 'title', 'title_link', 'title_font_weight', 'type',
'unique_fields'];
@@ -76,17 +76,18 @@ public function __construct(&$graph)
}
// slightly more complicated options
+ $this->font_size = Number::units($graph->getOption('legend_font_size'));
$this->title_colour = $graph->getOption('legend_title_colour',
'legend_colour');
$this->title_font = $graph->getOption('legend_title_font', 'legend_font');
$this->title_font_adjust = $graph->getOption('legend_title_font_adjust',
'legend_font_adjust');
- $this->title_font_size = $graph->getOption('legend_title_font_size',
- 'legend_font_size');
- $this->line_spacing = $graph->getOption('legend_line_spacing');
+ $this->title_font_size = Number::units($graph->getOption('legend_title_font_size',
+ 'legend_font_size'));
+ $this->line_spacing = Number::units($graph->getOption('legend_line_spacing'));
if($this->line_spacing === null || $this->line_spacing < 1)
$this->line_spacing = $this->font_size;
- $this->title_line_spacing = $graph->getOption('legend_title_line_spacing');
+ $this->title_line_spacing = Number::units($graph->getOption('legend_title_line_spacing'));
if($this->title_line_spacing === null || $this->title_line_spacing < 1)
$this->title_line_spacing = $this->title_font_size;
}
diff --git a/classes/vendor/goat1000/svggraph/LegendEntry.php b/classes/vendor/74x/goat1000/svggraph/LegendEntry.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/LegendEntry.php
rename to classes/vendor/74x/goat1000/svggraph/LegendEntry.php
diff --git a/classes/vendor/goat1000/svggraph/Line.php b/classes/vendor/74x/goat1000/svggraph/Line.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/Line.php
rename to classes/vendor/74x/goat1000/svggraph/Line.php
diff --git a/classes/vendor/goat1000/svggraph/LineGraph.php b/classes/vendor/74x/goat1000/svggraph/LineGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/LineGraph.php
rename to classes/vendor/74x/goat1000/svggraph/LineGraph.php
diff --git a/classes/vendor/goat1000/svggraph/Marker.php b/classes/vendor/74x/goat1000/svggraph/Marker.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/Marker.php
rename to classes/vendor/74x/goat1000/svggraph/Marker.php
diff --git a/classes/vendor/goat1000/svggraph/MarkerShape.php b/classes/vendor/74x/goat1000/svggraph/MarkerShape.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/MarkerShape.php
rename to classes/vendor/74x/goat1000/svggraph/MarkerShape.php
diff --git a/classes/vendor/goat1000/svggraph/Markers.php b/classes/vendor/74x/goat1000/svggraph/Markers.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/Markers.php
rename to classes/vendor/74x/goat1000/svggraph/Markers.php
diff --git a/classes/vendor/74x/goat1000/svggraph/Matrix.php b/classes/vendor/74x/goat1000/svggraph/Matrix.php
new file mode 100644
index 0000000..a00b1e3
--- /dev/null
+++ b/classes/vendor/74x/goat1000/svggraph/Matrix.php
@@ -0,0 +1,346 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * Class for matrix maths
+ */
+class Matrix {
+ private $rows = 0;
+ private $cols = 0;
+ private $data = [];
+ const MAX_SIZE = 100000;
+
+ public function __construct($r, $c)
+ {
+ if(!is_int($c) || !is_int($r) || $c < 1 || $r < 1)
+ throw new \InvalidArgumentException("{$r}\u{00d7}{$c} matrix size invalid");
+ if($c * $r > Matrix::MAX_SIZE)
+ throw new \InvalidArgumentException("{$r}\u{00d7}{$c} matrix too large");
+
+ $this->rows = $r;
+ $this->cols = $c;
+ $this->data = array_fill(0, $c * $r, 0);
+ }
+
+ /**
+ * Returns the number of rows and cols in an array
+ */
+ public function dimensions()
+ {
+ return [$this->rows, $this->cols];
+ }
+
+ /**
+ * Returns the size as a string
+ */
+ public function size()
+ {
+ return $this->rows . "\u{00d7}" . $this->cols;
+ }
+
+ /**
+ * Element access
+ */
+ public function &__invoke($r, $c, $v = null)
+ {
+ if($c < 0 || $r < 0 || $c >= $this->cols || $r >= $this->rows)
+ throw new \InvalidArgumentException("({$r},{$c}) out of range of " .
+ $this->size() . " matrix");
+
+ $item = $this->cols * $r + $c;
+ if($v !== null) {
+ if(!is_numeric($v))
+ throw new \InvalidArgumentException("Matrix values must be numeric");
+ if(is_string($v))
+ $this->data[$item] = $v;
+ elseif(is_int($v))
+ $this->data[$item] = (string)$v;
+ else
+ $this->data[$item] = sprintf("%.40F", $v);
+ }
+ return $this->data[$item];
+ }
+
+ /**
+ * Fill the array with values (row-major order)
+ */
+ public function load(array $values)
+ {
+ for($i = 0; $i < $this->rows * $this->cols; ++$i) {
+ $value = $values[$i];
+ if(isset($value) && is_numeric($value)) {
+ $this->data[$i] = is_int($value) ? (string)$value :
+ sprintf("%.40F", $value);
+ }
+ }
+ }
+
+ /**
+ * Sets the identity matrix
+ */
+ public function identity()
+ {
+ if($this->rows != $this->cols)
+ throw new \Exception($this->size() . " not a square matrix");
+ $this->data = array_fill(0, $this->rows * $this->cols, 0);
+ for($i = 0; $i < $this->rows; ++$i)
+ $this->data[($i * $this->rows) + $i] = 1;
+ }
+
+ /**
+ * Return the transpose of the matrix
+ */
+ public function transpose()
+ {
+ $tc = $this->rows;
+ $tr = $this->cols;
+ $result = new Matrix($tr, $tc);
+
+ // row or column vector = same data when transposed
+ if($tc == 1 || $tr == 1) {
+ $result->data = $this->data;
+ return $result;
+ }
+
+ for($c = 0; $c < $tc; ++$c) {
+ for($r = 0; $r < $tr; ++$r) {
+ $result($r, $c, $this($c, $r));
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Add two matrices
+ */
+ public function add(Matrix $m, $bcscale = 50)
+ {
+ if($m->rows != $this->rows || $m->cols != $this->cols)
+ throw new \InvalidArgumentException("Cannot add " . $this->size() .
+ " and " . $m->size() . " matrices.");
+
+ $old_scale = bcscale($bcscale);
+ $result = new Matrix($this->cols, $this->rows);
+ foreach($this->data as $k => $value)
+ $result->data[$k] = bcadd($value, $m->data[$k]);
+
+ bcscale($old_scale);
+ return $result;
+ }
+
+ /**
+ * Subtract a matrix
+ */
+ public function subtract(Matrix $m, $bcscale = 50)
+ {
+ if($m->rows != $this->rows || $m->cols != $this->cols)
+ throw new \InvalidArgumentException("Cannot subtract " . $m->size() .
+ " matrix from " . $this->size() . " matrix.");
+
+ $old_scale = bcscale($bcscale);
+ $result = new Matrix($this->cols, $this->rows);
+ foreach($this->data as $k => $value)
+ $result->data[$k] = bcsub($value, $m->data[$k]);
+
+ bcscale($old_scale);
+ return $result;
+ }
+
+ /**
+ * Multiplication by a matrix
+ */
+ public function multiply(Matrix $m1, $bcscale = 50)
+ {
+ if($this->cols != $m1->rows)
+ throw new \InvalidArgumentException("Cannot multiply " . $this->size() .
+ " matrix by " . $m1->size() . " matrix.");
+ $m = $this->rows;
+ $n = $this->cols;
+ $p = $m1->cols;
+
+ $old_scale = bcscale($bcscale);
+ $result = new Matrix($m, $p);
+ for($i = 0; $i < $m; ++$i) {
+ for($j = 0; $j < $p; ++$j) {
+ $value = "0";
+ for($k = 0; $k < $n; ++$k) {
+ $value = bcadd($value, bcmul($this($i, $k), $m1($k, $j)));
+ }
+ $result($i, $j, $value);
+ }
+ }
+
+ bcscale($old_scale);
+ return $result;
+ }
+
+ /**
+ * Gaussian elimination
+ */
+ public function gaussian($bcscale = 50)
+ {
+ $old_scale = bcscale($bcscale);
+
+ $argmax = function($a, $b, $m, $col) {
+ $max = 0;
+ $max_i = 0;
+ for($i = $a; $i < $b; ++$i) {
+ $value = $m($a, $col);
+ if(bccomp($value, "0") == -1)
+ $value = bcmul($value, "-1");
+ if(bccomp($value, $max) == 1) {
+ $max_i = $i;
+ $max = $value;
+ }
+ return $max_i;
+ }
+ };
+
+ $m = $this->rows;
+ $n = $this->cols;
+ $h = $k = 0;
+ while($h < $m && $k < $n) {
+ $i_max = $argmax($h, $m, $this, $k);
+ if($this($i_max, $k) == 0) {
+ ++$k;
+ } else {
+ $this->rowSwap($h, $i_max);
+ for($i = $h + 1; $i < $m; ++$i) {
+ $f = bcdiv($this($i, $k), $this($h, $k));
+ $this($i, $k, 0);
+ for($j = $k + 1; $j < $n; ++$j) {
+ $val = bcsub($this($i, $j), bcmul($this($h, $j), $f));
+ $this($i, $j, $val);
+ }
+ }
+ ++$h;
+ ++$k;
+ }
+ }
+
+ bcscale($old_scale);
+ }
+
+ /**
+ * Use Gaussian elimination to solve the equations with given RHS
+ */
+ public function gaussian_solve(Matrix $rhs, $bcscale = 50)
+ {
+ $a = $this->augment($rhs);
+ $a->gaussian($bcscale);
+ return $this->solve($a, $bcscale);
+ }
+
+ /**
+ * Creates a new matrix with $this on left and $rhs on right
+ */
+ public function augment(Matrix $rhs)
+ {
+ $m = $this->rows;
+ $n = $this->cols + $rhs->cols;
+ $aug = new Matrix($m, $n);
+
+ $c = 0;
+ for($i = 0; $i < $m; ++$i) {
+ for($j = 0; $j < $this->cols; ++$j)
+ $aug->data[$c++] = $this($i, $j);
+ for($j = 0; $j < $rhs->cols; ++$j)
+ $aug->data[$c++] = $rhs($i, $j);
+ }
+ return $aug;
+ }
+
+ /**
+ * Solves simultaneous equations using Gaussian elimination
+ */
+ public function solve(Matrix $a, $bcscale)
+ {
+ $result = new Matrix(1, $a->rows);
+ $old_scale = bcscale($bcscale);
+
+ // back substitution
+ $m = $a->rows;
+ $n = $a->cols;
+ for($i = $m - 1; $i >= 0; --$i) {
+ for($j = $n - 2; $j > $i; --$j) {
+ $value = bcsub($a($i, $n - 1),
+ bcmul($a($i, $j), $result(0, $j)));
+ $a($i, $n - 1, $value);
+ $a($i, $j, 0);
+ }
+ $d = $a($i, $i);
+ if($d == 0)
+ return null;
+ $value = bcdiv($a($i, $n - 1), $a($i, $i));
+ $result(0, $i, $value);
+ }
+
+ bcscale($old_scale);
+ return $result;
+ }
+
+ /**
+ * Swaps two rows
+ */
+ public function rowSwap($r1, $r2)
+ {
+ for($i = 0; $i < $this->cols; ++$i) {
+ $c = $this($r1, $i);
+ $this($r1, $i, $this($r2, $i));
+ $this($r2, $i, $c);
+ }
+ }
+
+ /**
+ * Output as string for debugging
+ */
+ public function __toString()
+ {
+ $str = '';
+ $m = 0;
+ foreach($this->data as $v) {
+ $m1 = abs($v);
+ if($m1 > $m)
+ $m = $m1;
+ }
+
+ $digits = max(9,(int)log($m, 10) + 6);
+ for($r = 0; $r < $this->rows; ++$r) {
+ $str .= "\t";
+ $r_offset = $r * $this->cols;
+ for($c = 0; $c < $this->cols; ++$c) {
+ $str .= sprintf(" %{$digits}.4f", $this->data[$r_offset + $c]);
+ }
+ $str .= "\n";
+ }
+ return $str;
+ }
+
+ /**
+ * Returns the data array
+ */
+ public function asArray()
+ {
+ return $this->data;
+ }
+}
diff --git a/classes/vendor/goat1000/svggraph/MultiGraph.php b/classes/vendor/74x/goat1000/svggraph/MultiGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/MultiGraph.php
rename to classes/vendor/74x/goat1000/svggraph/MultiGraph.php
diff --git a/classes/vendor/goat1000/svggraph/MultiGraphTrait.php b/classes/vendor/74x/goat1000/svggraph/MultiGraphTrait.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/MultiGraphTrait.php
rename to classes/vendor/74x/goat1000/svggraph/MultiGraphTrait.php
diff --git a/classes/vendor/goat1000/svggraph/MultiLineGraph.php b/classes/vendor/74x/goat1000/svggraph/MultiLineGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/MultiLineGraph.php
rename to classes/vendor/74x/goat1000/svggraph/MultiLineGraph.php
diff --git a/classes/vendor/goat1000/svggraph/MultiRadarGraph.php b/classes/vendor/74x/goat1000/svggraph/MultiRadarGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/MultiRadarGraph.php
rename to classes/vendor/74x/goat1000/svggraph/MultiRadarGraph.php
diff --git a/classes/vendor/goat1000/svggraph/MultiScatterGraph.php b/classes/vendor/74x/goat1000/svggraph/MultiScatterGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/MultiScatterGraph.php
rename to classes/vendor/74x/goat1000/svggraph/MultiScatterGraph.php
diff --git a/classes/vendor/goat1000/svggraph/MultiSteppedLineGraph.php b/classes/vendor/74x/goat1000/svggraph/MultiSteppedLineGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/MultiSteppedLineGraph.php
rename to classes/vendor/74x/goat1000/svggraph/MultiSteppedLineGraph.php
diff --git a/classes/vendor/goat1000/svggraph/Number.php b/classes/vendor/74x/goat1000/svggraph/Number.php
similarity index 84%
rename from classes/vendor/goat1000/svggraph/Number.php
rename to classes/vendor/74x/goat1000/svggraph/Number.php
index 504198d..24fb373 100644
--- a/classes/vendor/goat1000/svggraph/Number.php
+++ b/classes/vendor/74x/goat1000/svggraph/Number.php
@@ -1,6 +1,6 @@
units_before . $s . $this->units;
}
+
+ /**
+ * Converts a string with units to a value in SVG user units
+ */
+ public static function units($value)
+ {
+ if(is_numeric($value) || $value === null)
+ return $value;
+ if(!is_string($value))
+ throw new \InvalidArgumentException("Unit value not a string");
+
+ if(!preg_match('/^([0-9.]+)(px|in|cm|mm|pt|pc)$/', $value, $parts))
+ throw new \InvalidArgumentException("Unit value {$value} not in supported format");
+
+ $count = (float)$parts[1];
+ $units = $parts[2];
+ $umap = [
+ 'px' => 1.0, 'in' => 96.0, 'cm' => 37.795, 'mm' => 3.7795, 'pt' => 1.3333, 'pc' => 16.0
+ ];
+ return $count * $umap[$units];
+ }
}
diff --git a/classes/vendor/goat1000/svggraph/ParetoChart.php b/classes/vendor/74x/goat1000/svggraph/ParetoChart.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/ParetoChart.php
rename to classes/vendor/74x/goat1000/svggraph/ParetoChart.php
diff --git a/classes/vendor/goat1000/svggraph/Path.php b/classes/vendor/74x/goat1000/svggraph/Path.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/Path.php
rename to classes/vendor/74x/goat1000/svggraph/Path.php
diff --git a/classes/vendor/goat1000/svggraph/PathData.php b/classes/vendor/74x/goat1000/svggraph/PathData.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/PathData.php
rename to classes/vendor/74x/goat1000/svggraph/PathData.php
diff --git a/classes/vendor/goat1000/svggraph/PatternList.php b/classes/vendor/74x/goat1000/svggraph/PatternList.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/PatternList.php
rename to classes/vendor/74x/goat1000/svggraph/PatternList.php
diff --git a/classes/vendor/goat1000/svggraph/Pie3DGraph.php b/classes/vendor/74x/goat1000/svggraph/Pie3DGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/Pie3DGraph.php
rename to classes/vendor/74x/goat1000/svggraph/Pie3DGraph.php
diff --git a/classes/vendor/goat1000/svggraph/PieExploder.php b/classes/vendor/74x/goat1000/svggraph/PieExploder.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/PieExploder.php
rename to classes/vendor/74x/goat1000/svggraph/PieExploder.php
diff --git a/classes/vendor/goat1000/svggraph/PieGraph.php b/classes/vendor/74x/goat1000/svggraph/PieGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/PieGraph.php
rename to classes/vendor/74x/goat1000/svggraph/PieGraph.php
diff --git a/classes/vendor/goat1000/svggraph/PieSliceEdge.php b/classes/vendor/74x/goat1000/svggraph/PieSliceEdge.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/PieSliceEdge.php
rename to classes/vendor/74x/goat1000/svggraph/PieSliceEdge.php
diff --git a/classes/vendor/goat1000/svggraph/Point.php b/classes/vendor/74x/goat1000/svggraph/Point.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/Point.php
rename to classes/vendor/74x/goat1000/svggraph/Point.php
diff --git a/classes/vendor/goat1000/svggraph/PointGraph.php b/classes/vendor/74x/goat1000/svggraph/PointGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/PointGraph.php
rename to classes/vendor/74x/goat1000/svggraph/PointGraph.php
diff --git a/classes/vendor/goat1000/svggraph/PolarArea3DGraph.php b/classes/vendor/74x/goat1000/svggraph/PolarArea3DGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/PolarArea3DGraph.php
rename to classes/vendor/74x/goat1000/svggraph/PolarArea3DGraph.php
diff --git a/classes/vendor/goat1000/svggraph/PolarAreaGraph.php b/classes/vendor/74x/goat1000/svggraph/PolarAreaGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/PolarAreaGraph.php
rename to classes/vendor/74x/goat1000/svggraph/PolarAreaGraph.php
diff --git a/classes/vendor/goat1000/svggraph/PolarAreaTrait.php b/classes/vendor/74x/goat1000/svggraph/PolarAreaTrait.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/PolarAreaTrait.php
rename to classes/vendor/74x/goat1000/svggraph/PolarAreaTrait.php
diff --git a/classes/vendor/goat1000/svggraph/PolyLine.php b/classes/vendor/74x/goat1000/svggraph/PolyLine.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/PolyLine.php
rename to classes/vendor/74x/goat1000/svggraph/PolyLine.php
diff --git a/classes/vendor/goat1000/svggraph/Polygon.php b/classes/vendor/74x/goat1000/svggraph/Polygon.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/Polygon.php
rename to classes/vendor/74x/goat1000/svggraph/Polygon.php
diff --git a/classes/vendor/goat1000/svggraph/PopulationPyramid.php b/classes/vendor/74x/goat1000/svggraph/PopulationPyramid.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/PopulationPyramid.php
rename to classes/vendor/74x/goat1000/svggraph/PopulationPyramid.php
diff --git a/classes/vendor/goat1000/svggraph/PopulationPyramidAverage.php b/classes/vendor/74x/goat1000/svggraph/PopulationPyramidAverage.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/PopulationPyramidAverage.php
rename to classes/vendor/74x/goat1000/svggraph/PopulationPyramidAverage.php
diff --git a/classes/vendor/goat1000/svggraph/README.md b/classes/vendor/74x/goat1000/svggraph/README.md
similarity index 100%
rename from classes/vendor/goat1000/svggraph/README.md
rename to classes/vendor/74x/goat1000/svggraph/README.md
diff --git a/classes/vendor/goat1000/svggraph/README.txt b/classes/vendor/74x/goat1000/svggraph/README.txt
similarity index 99%
rename from classes/vendor/goat1000/svggraph/README.txt
rename to classes/vendor/74x/goat1000/svggraph/README.txt
index 0012f90..31cc981 100644
--- a/classes/vendor/goat1000/svggraph/README.txt
+++ b/classes/vendor/74x/goat1000/svggraph/README.txt
@@ -1,4 +1,4 @@
-SVGGraph Library version 3.19
+SVGGraph Library version 3.20
=============================
This library provides PHP classes and functions for easily creating SVG
diff --git a/classes/vendor/goat1000/svggraph/RGBColour.php b/classes/vendor/74x/goat1000/svggraph/RGBColour.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/RGBColour.php
rename to classes/vendor/74x/goat1000/svggraph/RGBColour.php
diff --git a/classes/vendor/goat1000/svggraph/RadarGraph.php b/classes/vendor/74x/goat1000/svggraph/RadarGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/RadarGraph.php
rename to classes/vendor/74x/goat1000/svggraph/RadarGraph.php
diff --git a/classes/vendor/goat1000/svggraph/Rect.php b/classes/vendor/74x/goat1000/svggraph/Rect.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/Rect.php
rename to classes/vendor/74x/goat1000/svggraph/Rect.php
diff --git a/classes/vendor/goat1000/svggraph/SVGGraph.php b/classes/vendor/74x/goat1000/svggraph/SVGGraph.php
similarity index 98%
rename from classes/vendor/goat1000/svggraph/SVGGraph.php
rename to classes/vendor/74x/goat1000/svggraph/SVGGraph.php
index 16a2fd1..6c7b3d9 100644
--- a/classes/vendor/goat1000/svggraph/SVGGraph.php
+++ b/classes/vendor/74x/goat1000/svggraph/SVGGraph.php
@@ -25,7 +25,7 @@ class SVGGraph {
use SVGGraphTrait;
- const VERSION = 'SVGGraph 3.19';
+ const VERSION = 'SVGGraph 3.20';
private $width = 100;
private $height = 100;
private $settings = [];
diff --git a/classes/vendor/goat1000/svggraph/SVGGraphTrait.php b/classes/vendor/74x/goat1000/svggraph/SVGGraphTrait.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/SVGGraphTrait.php
rename to classes/vendor/74x/goat1000/svggraph/SVGGraphTrait.php
diff --git a/classes/vendor/goat1000/svggraph/ScatterGraph.php b/classes/vendor/74x/goat1000/svggraph/ScatterGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/ScatterGraph.php
rename to classes/vendor/74x/goat1000/svggraph/ScatterGraph.php
diff --git a/classes/vendor/goat1000/svggraph/SemiDonut3DGraph.php b/classes/vendor/74x/goat1000/svggraph/SemiDonut3DGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/SemiDonut3DGraph.php
rename to classes/vendor/74x/goat1000/svggraph/SemiDonut3DGraph.php
diff --git a/classes/vendor/goat1000/svggraph/SemiDonutGraph.php b/classes/vendor/74x/goat1000/svggraph/SemiDonutGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/SemiDonutGraph.php
rename to classes/vendor/74x/goat1000/svggraph/SemiDonutGraph.php
diff --git a/classes/vendor/goat1000/svggraph/Shape.php b/classes/vendor/74x/goat1000/svggraph/Shape.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/Shape.php
rename to classes/vendor/74x/goat1000/svggraph/Shape.php
diff --git a/classes/vendor/goat1000/svggraph/ShapeList.php b/classes/vendor/74x/goat1000/svggraph/ShapeList.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/ShapeList.php
rename to classes/vendor/74x/goat1000/svggraph/ShapeList.php
diff --git a/classes/vendor/goat1000/svggraph/SliceInfo.php b/classes/vendor/74x/goat1000/svggraph/SliceInfo.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/SliceInfo.php
rename to classes/vendor/74x/goat1000/svggraph/SliceInfo.php
diff --git a/classes/vendor/goat1000/svggraph/StackedBar3DGraph.php b/classes/vendor/74x/goat1000/svggraph/StackedBar3DGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/StackedBar3DGraph.php
rename to classes/vendor/74x/goat1000/svggraph/StackedBar3DGraph.php
diff --git a/classes/vendor/goat1000/svggraph/StackedBarAndLineGraph.php b/classes/vendor/74x/goat1000/svggraph/StackedBarAndLineGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/StackedBarAndLineGraph.php
rename to classes/vendor/74x/goat1000/svggraph/StackedBarAndLineGraph.php
diff --git a/classes/vendor/goat1000/svggraph/StackedBarGraph.php b/classes/vendor/74x/goat1000/svggraph/StackedBarGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/StackedBarGraph.php
rename to classes/vendor/74x/goat1000/svggraph/StackedBarGraph.php
diff --git a/classes/vendor/goat1000/svggraph/StackedBarTrait.php b/classes/vendor/74x/goat1000/svggraph/StackedBarTrait.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/StackedBarTrait.php
rename to classes/vendor/74x/goat1000/svggraph/StackedBarTrait.php
diff --git a/classes/vendor/goat1000/svggraph/StackedCylinderGraph.php b/classes/vendor/74x/goat1000/svggraph/StackedCylinderGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/StackedCylinderGraph.php
rename to classes/vendor/74x/goat1000/svggraph/StackedCylinderGraph.php
diff --git a/classes/vendor/goat1000/svggraph/StackedGrouped3DGraphTrait.php b/classes/vendor/74x/goat1000/svggraph/StackedGrouped3DGraphTrait.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/StackedGrouped3DGraphTrait.php
rename to classes/vendor/74x/goat1000/svggraph/StackedGrouped3DGraphTrait.php
diff --git a/classes/vendor/goat1000/svggraph/StackedGroupedBar3DGraph.php b/classes/vendor/74x/goat1000/svggraph/StackedGroupedBar3DGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/StackedGroupedBar3DGraph.php
rename to classes/vendor/74x/goat1000/svggraph/StackedGroupedBar3DGraph.php
diff --git a/classes/vendor/goat1000/svggraph/StackedGroupedBarGraph.php b/classes/vendor/74x/goat1000/svggraph/StackedGroupedBarGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/StackedGroupedBarGraph.php
rename to classes/vendor/74x/goat1000/svggraph/StackedGroupedBarGraph.php
diff --git a/classes/vendor/goat1000/svggraph/StackedGroupedBarTrait.php b/classes/vendor/74x/goat1000/svggraph/StackedGroupedBarTrait.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/StackedGroupedBarTrait.php
rename to classes/vendor/74x/goat1000/svggraph/StackedGroupedBarTrait.php
diff --git a/classes/vendor/goat1000/svggraph/StackedGroupedCylinderGraph.php b/classes/vendor/74x/goat1000/svggraph/StackedGroupedCylinderGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/StackedGroupedCylinderGraph.php
rename to classes/vendor/74x/goat1000/svggraph/StackedGroupedCylinderGraph.php
diff --git a/classes/vendor/goat1000/svggraph/StackedLineGraph.php b/classes/vendor/74x/goat1000/svggraph/StackedLineGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/StackedLineGraph.php
rename to classes/vendor/74x/goat1000/svggraph/StackedLineGraph.php
diff --git a/classes/vendor/goat1000/svggraph/SteppedLineGraph.php b/classes/vendor/74x/goat1000/svggraph/SteppedLineGraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/SteppedLineGraph.php
rename to classes/vendor/74x/goat1000/svggraph/SteppedLineGraph.php
diff --git a/classes/vendor/goat1000/svggraph/SteppedLineTrait.php b/classes/vendor/74x/goat1000/svggraph/SteppedLineTrait.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/SteppedLineTrait.php
rename to classes/vendor/74x/goat1000/svggraph/SteppedLineTrait.php
diff --git a/classes/vendor/goat1000/svggraph/StructuredData.php b/classes/vendor/74x/goat1000/svggraph/StructuredData.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/StructuredData.php
rename to classes/vendor/74x/goat1000/svggraph/StructuredData.php
diff --git a/classes/vendor/goat1000/svggraph/StructuredDataItem.php b/classes/vendor/74x/goat1000/svggraph/StructuredDataItem.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/StructuredDataItem.php
rename to classes/vendor/74x/goat1000/svggraph/StructuredDataItem.php
diff --git a/classes/vendor/goat1000/svggraph/StructuredDataIterator.php b/classes/vendor/74x/goat1000/svggraph/StructuredDataIterator.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/StructuredDataIterator.php
rename to classes/vendor/74x/goat1000/svggraph/StructuredDataIterator.php
diff --git a/classes/vendor/goat1000/svggraph/Subgraph.php b/classes/vendor/74x/goat1000/svggraph/Subgraph.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/Subgraph.php
rename to classes/vendor/74x/goat1000/svggraph/Subgraph.php
diff --git a/classes/vendor/goat1000/svggraph/Symbols.php b/classes/vendor/74x/goat1000/svggraph/Symbols.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/Symbols.php
rename to classes/vendor/74x/goat1000/svggraph/Symbols.php
diff --git a/classes/vendor/goat1000/svggraph/Text.php b/classes/vendor/74x/goat1000/svggraph/Text.php
similarity index 97%
rename from classes/vendor/goat1000/svggraph/Text.php
rename to classes/vendor/74x/goat1000/svggraph/Text.php
index b72abd5..da3dd69 100644
--- a/classes/vendor/goat1000/svggraph/Text.php
+++ b/classes/vendor/74x/goat1000/svggraph/Text.php
@@ -1,6 +1,6 @@
0 ? $this->splitLines($text) : [$text];
$width = $height = 0;
foreach($lines as $l) {
@@ -259,6 +265,9 @@ private function getCacheKey($text, $font_size, $angle, $line_spacing)
*/
public function baseline($font_size)
{
+ if(!is_numeric($font_size))
+ throw new \InvalidArgumentException("Font size is not numeric");
+
$metrics = $this->no_metrics ? false : $this->loadMetrics($this->font);
if($metrics) {
@@ -298,6 +307,9 @@ public function text($text, $line_spacing, $attribs, $styles = null)
return $this->graph->element('text', $attribs, $styles, $content);
}
+ if($line_spacing !== 0 && !is_numeric($line_spacing))
+ throw new \InvalidArgumentException("Line spacing is not numeric");
+
$lines = $this->splitLines($text);
$content = '';
$group = 'text';
diff --git a/classes/vendor/goat1000/svggraph/TextClass.php b/classes/vendor/74x/goat1000/svggraph/TextClass.php
similarity index 100%
rename from classes/vendor/goat1000/svggraph/TextClass.php
rename to classes/vendor/74x/goat1000/svggraph/TextClass.php
diff --git a/classes/vendor/goat1000/svggraph/TextShape.php b/classes/vendor/74x/goat1000/svggraph/TextShape.php
similarity index 88%
rename from classes/vendor/goat1000/svggraph/TextShape.php
rename to classes/vendor/74x/goat1000/svggraph/TextShape.php
index bf9e7c7..04c5926 100644
--- a/classes/vendor/goat1000/svggraph/TextShape.php
+++ b/classes/vendor/74x/goat1000/svggraph/TextShape.php
@@ -1,6 +1,6 @@
+ * Jordi Boggiano
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Autoload;
+
+/**
+ * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
+ *
+ * $loader = new \Composer\Autoload\ClassLoader();
+ *
+ * // register classes with namespaces
+ * $loader->add('Symfony\Component', __DIR__.'/component');
+ * $loader->add('Symfony', __DIR__.'/framework');
+ *
+ * // activate the autoloader
+ * $loader->register();
+ *
+ * // to enable searching the include path (eg. for PEAR packages)
+ * $loader->setUseIncludePath(true);
+ *
+ * In this example, if you try to use a class in the Symfony\Component
+ * namespace or one of its children (Symfony\Component\Console for instance),
+ * the autoloader will first look for the class under the component/
+ * directory, and it will then fallback to the framework/ directory if not
+ * found before giving up.
+ *
+ * This class is loosely based on the Symfony UniversalClassLoader.
+ *
+ * @author Fabien Potencier
+ * @author Jordi Boggiano
+ * @see https://www.php-fig.org/psr/psr-0/
+ * @see https://www.php-fig.org/psr/psr-4/
+ */
+class ClassLoader
+{
+ /** @var \Closure(string):void */
+ private static $includeFile;
+
+ /** @var string|null */
+ private $vendorDir;
+
+ // PSR-4
+ /**
+ * @var array>
+ */
+ private $prefixLengthsPsr4 = array();
+ /**
+ * @var array>
+ */
+ private $prefixDirsPsr4 = array();
+ /**
+ * @var list
+ */
+ private $fallbackDirsPsr4 = array();
+
+ // PSR-0
+ /**
+ * List of PSR-0 prefixes
+ *
+ * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
+ *
+ * @var array>>
+ */
+ private $prefixesPsr0 = array();
+ /**
+ * @var list
+ */
+ private $fallbackDirsPsr0 = array();
+
+ /** @var bool */
+ private $useIncludePath = false;
+
+ /**
+ * @var array
+ */
+ private $classMap = array();
+
+ /** @var bool */
+ private $classMapAuthoritative = false;
+
+ /**
+ * @var array
+ */
+ private $missingClasses = array();
+
+ /** @var string|null */
+ private $apcuPrefix;
+
+ /**
+ * @var array
+ */
+ private static $registeredLoaders = array();
+
+ /**
+ * @param string|null $vendorDir
+ */
+ public function __construct($vendorDir = null)
+ {
+ $this->vendorDir = $vendorDir;
+ self::initializeIncludeClosure();
+ }
+
+ /**
+ * @return array>
+ */
+ public function getPrefixes()
+ {
+ if (!empty($this->prefixesPsr0)) {
+ return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
+ }
+
+ return array();
+ }
+
+ /**
+ * @return array>
+ */
+ public function getPrefixesPsr4()
+ {
+ return $this->prefixDirsPsr4;
+ }
+
+ /**
+ * @return list
+ */
+ public function getFallbackDirs()
+ {
+ return $this->fallbackDirsPsr0;
+ }
+
+ /**
+ * @return list
+ */
+ public function getFallbackDirsPsr4()
+ {
+ return $this->fallbackDirsPsr4;
+ }
+
+ /**
+ * @return array Array of classname => path
+ */
+ public function getClassMap()
+ {
+ return $this->classMap;
+ }
+
+ /**
+ * @param array $classMap Class to filename map
+ *
+ * @return void
+ */
+ public function addClassMap(array $classMap)
+ {
+ if ($this->classMap) {
+ $this->classMap = array_merge($this->classMap, $classMap);
+ } else {
+ $this->classMap = $classMap;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix, either
+ * appending or prepending to the ones previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param list|string $paths The PSR-0 root directories
+ * @param bool $prepend Whether to prepend the directories
+ *
+ * @return void
+ */
+ public function add($prefix, $paths, $prepend = false)
+ {
+ $paths = (array) $paths;
+ if (!$prefix) {
+ if ($prepend) {
+ $this->fallbackDirsPsr0 = array_merge(
+ $paths,
+ $this->fallbackDirsPsr0
+ );
+ } else {
+ $this->fallbackDirsPsr0 = array_merge(
+ $this->fallbackDirsPsr0,
+ $paths
+ );
+ }
+
+ return;
+ }
+
+ $first = $prefix[0];
+ if (!isset($this->prefixesPsr0[$first][$prefix])) {
+ $this->prefixesPsr0[$first][$prefix] = $paths;
+
+ return;
+ }
+ if ($prepend) {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ $paths,
+ $this->prefixesPsr0[$first][$prefix]
+ );
+ } else {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ $this->prefixesPsr0[$first][$prefix],
+ $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace, either
+ * appending or prepending to the ones previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param list|string $paths The PSR-4 base directories
+ * @param bool $prepend Whether to prepend the directories
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return void
+ */
+ public function addPsr4($prefix, $paths, $prepend = false)
+ {
+ $paths = (array) $paths;
+ if (!$prefix) {
+ // Register directories for the root namespace.
+ if ($prepend) {
+ $this->fallbackDirsPsr4 = array_merge(
+ $paths,
+ $this->fallbackDirsPsr4
+ );
+ } else {
+ $this->fallbackDirsPsr4 = array_merge(
+ $this->fallbackDirsPsr4,
+ $paths
+ );
+ }
+ } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
+ // Register directories for a new namespace.
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = $paths;
+ } elseif ($prepend) {
+ // Prepend directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ $paths,
+ $this->prefixDirsPsr4[$prefix]
+ );
+ } else {
+ // Append directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ $this->prefixDirsPsr4[$prefix],
+ $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix,
+ * replacing any others previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param list|string $paths The PSR-0 base directories
+ *
+ * @return void
+ */
+ public function set($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr0 = (array) $paths;
+ } else {
+ $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace,
+ * replacing any others previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param list|string $paths The PSR-4 base directories
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return void
+ */
+ public function setPsr4($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr4 = (array) $paths;
+ } else {
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Turns on searching the include path for class files.
+ *
+ * @param bool $useIncludePath
+ *
+ * @return void
+ */
+ public function setUseIncludePath($useIncludePath)
+ {
+ $this->useIncludePath = $useIncludePath;
+ }
+
+ /**
+ * Can be used to check if the autoloader uses the include path to check
+ * for classes.
+ *
+ * @return bool
+ */
+ public function getUseIncludePath()
+ {
+ return $this->useIncludePath;
+ }
+
+ /**
+ * Turns off searching the prefix and fallback directories for classes
+ * that have not been registered with the class map.
+ *
+ * @param bool $classMapAuthoritative
+ *
+ * @return void
+ */
+ public function setClassMapAuthoritative($classMapAuthoritative)
+ {
+ $this->classMapAuthoritative = $classMapAuthoritative;
+ }
+
+ /**
+ * Should class lookup fail if not found in the current class map?
+ *
+ * @return bool
+ */
+ public function isClassMapAuthoritative()
+ {
+ return $this->classMapAuthoritative;
+ }
+
+ /**
+ * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
+ *
+ * @param string|null $apcuPrefix
+ *
+ * @return void
+ */
+ public function setApcuPrefix($apcuPrefix)
+ {
+ $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
+ }
+
+ /**
+ * The APCu prefix in use, or null if APCu caching is not enabled.
+ *
+ * @return string|null
+ */
+ public function getApcuPrefix()
+ {
+ return $this->apcuPrefix;
+ }
+
+ /**
+ * Registers this instance as an autoloader.
+ *
+ * @param bool $prepend Whether to prepend the autoloader or not
+ *
+ * @return void
+ */
+ public function register($prepend = false)
+ {
+ spl_autoload_register(array($this, 'loadClass'), true, $prepend);
+
+ if (null === $this->vendorDir) {
+ return;
+ }
+
+ if ($prepend) {
+ self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
+ } else {
+ unset(self::$registeredLoaders[$this->vendorDir]);
+ self::$registeredLoaders[$this->vendorDir] = $this;
+ }
+ }
+
+ /**
+ * Unregisters this instance as an autoloader.
+ *
+ * @return void
+ */
+ public function unregister()
+ {
+ spl_autoload_unregister(array($this, 'loadClass'));
+
+ if (null !== $this->vendorDir) {
+ unset(self::$registeredLoaders[$this->vendorDir]);
+ }
+ }
+
+ /**
+ * Loads the given class or interface.
+ *
+ * @param string $class The name of the class
+ * @return true|null True if loaded, null otherwise
+ */
+ public function loadClass($class)
+ {
+ if ($file = $this->findFile($class)) {
+ $includeFile = self::$includeFile;
+ $includeFile($file);
+
+ return true;
+ }
+
+ return null;
+ }
+
+ /**
+ * Finds the path to the file where the class is defined.
+ *
+ * @param string $class The name of the class
+ *
+ * @return string|false The path if found, false otherwise
+ */
+ public function findFile($class)
+ {
+ // class map lookup
+ if (isset($this->classMap[$class])) {
+ return $this->classMap[$class];
+ }
+ if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
+ return false;
+ }
+ if (null !== $this->apcuPrefix) {
+ $file = apcu_fetch($this->apcuPrefix.$class, $hit);
+ if ($hit) {
+ return $file;
+ }
+ }
+
+ $file = $this->findFileWithExtension($class, '.php');
+
+ // Search for Hack files if we are running on HHVM
+ if (false === $file && defined('HHVM_VERSION')) {
+ $file = $this->findFileWithExtension($class, '.hh');
+ }
+
+ if (null !== $this->apcuPrefix) {
+ apcu_add($this->apcuPrefix.$class, $file);
+ }
+
+ if (false === $file) {
+ // Remember that this class does not exist.
+ $this->missingClasses[$class] = true;
+ }
+
+ return $file;
+ }
+
+ /**
+ * Returns the currently registered loaders keyed by their corresponding vendor directories.
+ *
+ * @return array
+ */
+ public static function getRegisteredLoaders()
+ {
+ return self::$registeredLoaders;
+ }
+
+ /**
+ * @param string $class
+ * @param string $ext
+ * @return string|false
+ */
+ private function findFileWithExtension($class, $ext)
+ {
+ // PSR-4 lookup
+ $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
+
+ $first = $class[0];
+ if (isset($this->prefixLengthsPsr4[$first])) {
+ $subPath = $class;
+ while (false !== $lastPos = strrpos($subPath, '\\')) {
+ $subPath = substr($subPath, 0, $lastPos);
+ $search = $subPath . '\\';
+ if (isset($this->prefixDirsPsr4[$search])) {
+ $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
+ foreach ($this->prefixDirsPsr4[$search] as $dir) {
+ if (file_exists($file = $dir . $pathEnd)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-4 fallback dirs
+ foreach ($this->fallbackDirsPsr4 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 lookup
+ if (false !== $pos = strrpos($class, '\\')) {
+ // namespaced class name
+ $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
+ . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
+ } else {
+ // PEAR-like class name
+ $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
+ }
+
+ if (isset($this->prefixesPsr0[$first])) {
+ foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
+ if (0 === strpos($class, $prefix)) {
+ foreach ($dirs as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-0 fallback dirs
+ foreach ($this->fallbackDirsPsr0 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 include paths.
+ if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
+ return $file;
+ }
+
+ return false;
+ }
+
+ /**
+ * @return void
+ */
+ private static function initializeIncludeClosure()
+ {
+ if (self::$includeFile !== null) {
+ return;
+ }
+
+ /**
+ * Scope isolated include.
+ *
+ * Prevents access to $this/self from included files.
+ *
+ * @param string $file
+ * @return void
+ */
+ self::$includeFile = \Closure::bind(static function($file) {
+ include $file;
+ }, null, null);
+ }
+}
diff --git a/classes/vendor/81x/composer/InstalledVersions.php b/classes/vendor/81x/composer/InstalledVersions.php
new file mode 100644
index 0000000..51e734a
--- /dev/null
+++ b/classes/vendor/81x/composer/InstalledVersions.php
@@ -0,0 +1,359 @@
+
+ * Jordi Boggiano
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer;
+
+use Composer\Autoload\ClassLoader;
+use Composer\Semver\VersionParser;
+
+/**
+ * This class is copied in every Composer installed project and available to all
+ *
+ * See also https://getcomposer.org/doc/07-runtime.md#installed-versions
+ *
+ * To require its presence, you can require `composer-runtime-api ^2.0`
+ *
+ * @final
+ */
+class InstalledVersions
+{
+ /**
+ * @var mixed[]|null
+ * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null
+ */
+ private static $installed;
+
+ /**
+ * @var bool|null
+ */
+ private static $canGetVendors;
+
+ /**
+ * @var array[]
+ * @psalm-var array}>
+ */
+ private static $installedByVendor = array();
+
+ /**
+ * Returns a list of all package names which are present, either by being installed, replaced or provided
+ *
+ * @return string[]
+ * @psalm-return list
+ */
+ public static function getInstalledPackages()
+ {
+ $packages = array();
+ foreach (self::getInstalled() as $installed) {
+ $packages[] = array_keys($installed['versions']);
+ }
+
+ if (1 === \count($packages)) {
+ return $packages[0];
+ }
+
+ return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
+ }
+
+ /**
+ * Returns a list of all package names with a specific type e.g. 'library'
+ *
+ * @param string $type
+ * @return string[]
+ * @psalm-return list
+ */
+ public static function getInstalledPackagesByType($type)
+ {
+ $packagesByType = array();
+
+ foreach (self::getInstalled() as $installed) {
+ foreach ($installed['versions'] as $name => $package) {
+ if (isset($package['type']) && $package['type'] === $type) {
+ $packagesByType[] = $name;
+ }
+ }
+ }
+
+ return $packagesByType;
+ }
+
+ /**
+ * Checks whether the given package is installed
+ *
+ * This also returns true if the package name is provided or replaced by another package
+ *
+ * @param string $packageName
+ * @param bool $includeDevRequirements
+ * @return bool
+ */
+ public static function isInstalled($packageName, $includeDevRequirements = true)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (isset($installed['versions'][$packageName])) {
+ return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks whether the given package satisfies a version constraint
+ *
+ * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
+ *
+ * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
+ *
+ * @param VersionParser $parser Install composer/semver to have access to this class and functionality
+ * @param string $packageName
+ * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
+ * @return bool
+ */
+ public static function satisfies(VersionParser $parser, $packageName, $constraint)
+ {
+ $constraint = $parser->parseConstraints((string) $constraint);
+ $provided = $parser->parseConstraints(self::getVersionRanges($packageName));
+
+ return $provided->matches($constraint);
+ }
+
+ /**
+ * Returns a version constraint representing all the range(s) which are installed for a given package
+ *
+ * It is easier to use this via isInstalled() with the $constraint argument if you need to check
+ * whether a given version of a package is installed, and not just whether it exists
+ *
+ * @param string $packageName
+ * @return string Version constraint usable with composer/semver
+ */
+ public static function getVersionRanges($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ $ranges = array();
+ if (isset($installed['versions'][$packageName]['pretty_version'])) {
+ $ranges[] = $installed['versions'][$packageName]['pretty_version'];
+ }
+ if (array_key_exists('aliases', $installed['versions'][$packageName])) {
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
+ }
+ if (array_key_exists('replaced', $installed['versions'][$packageName])) {
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
+ }
+ if (array_key_exists('provided', $installed['versions'][$packageName])) {
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
+ }
+
+ return implode(' || ', $ranges);
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
+ */
+ public static function getVersion($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ if (!isset($installed['versions'][$packageName]['version'])) {
+ return null;
+ }
+
+ return $installed['versions'][$packageName]['version'];
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
+ */
+ public static function getPrettyVersion($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ if (!isset($installed['versions'][$packageName]['pretty_version'])) {
+ return null;
+ }
+
+ return $installed['versions'][$packageName]['pretty_version'];
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
+ */
+ public static function getReference($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ if (!isset($installed['versions'][$packageName]['reference'])) {
+ return null;
+ }
+
+ return $installed['versions'][$packageName]['reference'];
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
+ */
+ public static function getInstallPath($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @return array
+ * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
+ */
+ public static function getRootPackage()
+ {
+ $installed = self::getInstalled();
+
+ return $installed[0]['root'];
+ }
+
+ /**
+ * Returns the raw installed.php data for custom implementations
+ *
+ * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
+ * @return array[]
+ * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}
+ */
+ public static function getRawData()
+ {
+ @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
+
+ if (null === self::$installed) {
+ // only require the installed.php file if this file is loaded from its dumped location,
+ // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
+ if (substr(__DIR__, -8, 1) !== 'C') {
+ self::$installed = include __DIR__ . '/installed.php';
+ } else {
+ self::$installed = array();
+ }
+ }
+
+ return self::$installed;
+ }
+
+ /**
+ * Returns the raw data of all installed.php which are currently loaded for custom implementations
+ *
+ * @return array[]
+ * @psalm-return list}>
+ */
+ public static function getAllRawData()
+ {
+ return self::getInstalled();
+ }
+
+ /**
+ * Lets you reload the static array from another file
+ *
+ * This is only useful for complex integrations in which a project needs to use
+ * this class but then also needs to execute another project's autoloader in process,
+ * and wants to ensure both projects have access to their version of installed.php.
+ *
+ * A typical case would be PHPUnit, where it would need to make sure it reads all
+ * the data it needs from this class, then call reload() with
+ * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
+ * the project in which it runs can then also use this class safely, without
+ * interference between PHPUnit's dependencies and the project's dependencies.
+ *
+ * @param array[] $data A vendor/composer/installed.php data set
+ * @return void
+ *
+ * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data
+ */
+ public static function reload($data)
+ {
+ self::$installed = $data;
+ self::$installedByVendor = array();
+ }
+
+ /**
+ * @return array[]
+ * @psalm-return list}>
+ */
+ private static function getInstalled()
+ {
+ if (null === self::$canGetVendors) {
+ self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
+ }
+
+ $installed = array();
+
+ if (self::$canGetVendors) {
+ foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
+ if (isset(self::$installedByVendor[$vendorDir])) {
+ $installed[] = self::$installedByVendor[$vendorDir];
+ } elseif (is_file($vendorDir.'/composer/installed.php')) {
+ /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */
+ $required = require $vendorDir.'/composer/installed.php';
+ $installed[] = self::$installedByVendor[$vendorDir] = $required;
+ if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
+ self::$installed = $installed[count($installed) - 1];
+ }
+ }
+ }
+ }
+
+ if (null === self::$installed) {
+ // only require the installed.php file if this file is loaded from its dumped location,
+ // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
+ if (substr(__DIR__, -8, 1) !== 'C') {
+ /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */
+ $required = require __DIR__ . '/installed.php';
+ self::$installed = $required;
+ } else {
+ self::$installed = array();
+ }
+ }
+
+ if (self::$installed !== array()) {
+ $installed[] = self::$installed;
+ }
+
+ return $installed;
+ }
+}
diff --git a/classes/vendor/81x/composer/LICENSE b/classes/vendor/81x/composer/LICENSE
new file mode 100644
index 0000000..f27399a
--- /dev/null
+++ b/classes/vendor/81x/composer/LICENSE
@@ -0,0 +1,21 @@
+
+Copyright (c) Nils Adermann, Jordi Boggiano
+
+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.
+
diff --git a/classes/vendor/81x/composer/autoload_classmap.php b/classes/vendor/81x/composer/autoload_classmap.php
new file mode 100644
index 0000000..e30c507
--- /dev/null
+++ b/classes/vendor/81x/composer/autoload_classmap.php
@@ -0,0 +1,10 @@
+ $vendorDir . '/composer/InstalledVersions.php',
+);
diff --git a/classes/vendor/81x/composer/autoload_namespaces.php b/classes/vendor/81x/composer/autoload_namespaces.php
new file mode 100644
index 0000000..e388449
--- /dev/null
+++ b/classes/vendor/81x/composer/autoload_namespaces.php
@@ -0,0 +1,9 @@
+ array($vendorDir . '/goat1000/svggraph'),
+ 'Dallgoot\\Yaml\\' => array($vendorDir . '/dallgoot/yaml/src'),
+);
diff --git a/classes/vendor/81x/composer/autoload_real.php b/classes/vendor/81x/composer/autoload_real.php
new file mode 100644
index 0000000..cb03020
--- /dev/null
+++ b/classes/vendor/81x/composer/autoload_real.php
@@ -0,0 +1,38 @@
+register(true);
+
+ return $loader;
+ }
+}
diff --git a/classes/vendor/81x/composer/autoload_static.php b/classes/vendor/81x/composer/autoload_static.php
new file mode 100644
index 0000000..78bbb3d
--- /dev/null
+++ b/classes/vendor/81x/composer/autoload_static.php
@@ -0,0 +1,44 @@
+
+ array (
+ 'Goat1000\\SVGGraph\\' => 18,
+ ),
+ 'D' =>
+ array (
+ 'Dallgoot\\Yaml\\' => 14,
+ ),
+ );
+
+ public static $prefixDirsPsr4 = array (
+ 'Goat1000\\SVGGraph\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/goat1000/svggraph',
+ ),
+ 'Dallgoot\\Yaml\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/dallgoot/yaml/src',
+ ),
+ );
+
+ public static $classMap = array (
+ 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
+ );
+
+ public static function getInitializer(ClassLoader $loader)
+ {
+ return \Closure::bind(function () use ($loader) {
+ $loader->prefixLengthsPsr4 = ComposerStaticInitb09adac1e675505f480ac5a16d86c6f1::$prefixLengthsPsr4;
+ $loader->prefixDirsPsr4 = ComposerStaticInitb09adac1e675505f480ac5a16d86c6f1::$prefixDirsPsr4;
+ $loader->classMap = ComposerStaticInitb09adac1e675505f480ac5a16d86c6f1::$classMap;
+
+ }, null, ClassLoader::class);
+ }
+}
diff --git a/classes/vendor/81x/composer/installed.json b/classes/vendor/81x/composer/installed.json
new file mode 100644
index 0000000..fa60abf
--- /dev/null
+++ b/classes/vendor/81x/composer/installed.json
@@ -0,0 +1,115 @@
+{
+ "packages": [
+ {
+ "name": "dallgoot/yaml",
+ "version": "0.9.1.1",
+ "version_normalized": "0.9.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/dallgoot/yaml.git",
+ "reference": "7e7ce911ad626ae35a6ea2086c000f5ee492607f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/dallgoot/yaml/zipball/7e7ce911ad626ae35a6ea2086c000f5ee492607f",
+ "reference": "7e7ce911ad626ae35a6ea2086c000f5ee492607f",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "ext-pcre": "*",
+ "ext-spl": "*",
+ "lib-pcre": "*",
+ "php": ">=8.1.14"
+ },
+ "require-dev": {
+ "ext-reflection": "*",
+ "phan/phan": "*",
+ "phpmetrics/phpmetrics": "*",
+ "phpunit/phpunit": "*"
+ },
+ "time": "2023-04-11T09:49:37+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Dallgoot\\Yaml\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "authors": [
+ {
+ "name": "dallgoot",
+ "email": "stephane.rebai@gmail.com"
+ }
+ ],
+ "description": "Provides loader, dumper and an API for YAML content. Loader builds to equivalent data types in PHP 8.x",
+ "homepage": "https://github.com/dallgoot/yaml",
+ "keywords": [
+ "parser",
+ "reader",
+ "writer",
+ "yaml"
+ ],
+ "support": {
+ "issues": "https://github.com/dallgoot/yaml/issues",
+ "source": "https://github.com/dallgoot/yaml/tree/0.9.1.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/dallgoot",
+ "type": "github"
+ }
+ ],
+ "install-path": "../dallgoot/yaml"
+ },
+ {
+ "name": "goat1000/svggraph",
+ "version": "3.20.0",
+ "version_normalized": "3.20.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/goat1000/SVGGraph.git",
+ "reference": "99576c9ad38763b8f10e6b03a9cff1ce32604869"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/goat1000/SVGGraph/zipball/99576c9ad38763b8f10e6b03a9cff1ce32604869",
+ "reference": "99576c9ad38763b8f10e6b03a9cff1ce32604869",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "lib-pcre": "*",
+ "php": ">=5.4.0"
+ },
+ "suggest": {
+ "ext-iconv": "For non-ASCII text measurement support"
+ },
+ "time": "2023-04-25T08:27:40+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Goat1000\\SVGGraph\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "LGPL-3.0-only"
+ ],
+ "description": "Generates SVG graphs",
+ "homepage": "http://www.goat1000.com/svggraph.php",
+ "support": {
+ "issues": "https://github.com/goat1000/SVGGraph/issues",
+ "source": "https://github.com/goat1000/SVGGraph/tree/3.20.0"
+ },
+ "install-path": "../goat1000/svggraph"
+ }
+ ],
+ "dev": true,
+ "dev-package-names": []
+}
diff --git a/classes/vendor/81x/composer/installed.php b/classes/vendor/81x/composer/installed.php
new file mode 100644
index 0000000..fdfd890
--- /dev/null
+++ b/classes/vendor/81x/composer/installed.php
@@ -0,0 +1,41 @@
+ array(
+ 'name' => 'bfh/verbalfeedback',
+ 'pretty_version' => '1.0.0+no-version-set',
+ 'version' => '1.0.0.0',
+ 'reference' => NULL,
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../../../../dependency_81x',
+ 'aliases' => array(),
+ 'dev' => true,
+ ),
+ 'versions' => array(
+ 'bfh/verbalfeedback' => array(
+ 'pretty_version' => '1.0.0+no-version-set',
+ 'version' => '1.0.0.0',
+ 'reference' => NULL,
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../../../../dependency_81x',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'dallgoot/yaml' => array(
+ 'pretty_version' => '0.9.1.1',
+ 'version' => '0.9.1.1',
+ 'reference' => '7e7ce911ad626ae35a6ea2086c000f5ee492607f',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../dallgoot/yaml',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'goat1000/svggraph' => array(
+ 'pretty_version' => '3.20.0',
+ 'version' => '3.20.0.0',
+ 'reference' => '99576c9ad38763b8f10e6b03a9cff1ce32604869',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../goat1000/svggraph',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ ),
+);
diff --git a/classes/vendor/81x/composer/platform_check.php b/classes/vendor/81x/composer/platform_check.php
new file mode 100644
index 0000000..941ac20
--- /dev/null
+++ b/classes/vendor/81x/composer/platform_check.php
@@ -0,0 +1,26 @@
+= 80114)) {
+ $issues[] = 'Your Composer dependencies require a PHP version ">= 8.1.14". You are running ' . PHP_VERSION . '.';
+}
+
+if ($issues) {
+ if (!headers_sent()) {
+ header('HTTP/1.1 500 Internal Server Error');
+ }
+ if (!ini_get('display_errors')) {
+ if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
+ fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
+ } elseif (!headers_sent()) {
+ 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
+ );
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/CHANGELOG.md b/classes/vendor/81x/dallgoot/yaml/CHANGELOG.md
new file mode 100644
index 0000000..4c51ec6
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/CHANGELOG.md
@@ -0,0 +1,30 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## 0.9.0.0
+
+### Changed
+
+- minimum PHP version supported is now 8.1.14
+- autoloading for PSR-4 (sorry to have polluted autoloads files worldwide 😢)
+- parsing in NodeFactory
+- logic in Dallgoot\Yaml\Dumper
+
+### Removed
+
+- support for PHP before 8.1.14
+- some dev dependencies
+- Dallgoot\Yaml class replaced by Dallgoot\Yaml\Yaml
+
+### Fixed
+
+- PHP Notice reported by in
+- dumping bug in Dallgoot\Yaml\DumperHandlers reported by in >
+
+## 0.3.2.0
+
+this version is not stable enough, nor finished, nor fully tested to be used in production, please prefer the 1.0.0
diff --git a/classes/vendor/81x/dallgoot/yaml/LICENSE b/classes/vendor/81x/dallgoot/yaml/LICENSE
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/classes/vendor/81x/dallgoot/yaml/README.md b/classes/vendor/81x/dallgoot/yaml/README.md
new file mode 100644
index 0000000..42fc084
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/README.md
@@ -0,0 +1,104 @@
+# Dallgoot : YAML library for PHP - Beta
+
+[![Build Status](https://api.travis-ci.com/dallgoot/yaml.svg?branch=master)](https://travis-ci.org/dallgoot/yaml) [![PHP from Packagist](https://img.shields.io/packagist/php-v/dallgoot/yaml)](https://packagist.org/packages/dallgoot/yaml) [![Packagist](https://img.shields.io/packagist/dt/dallgoot/yaml)](https://packagist.org/packages/dallgoot/yaml)
+[![Maintainability](https://api.codeclimate.com/v1/badges/dfae4b8e665a1d728e3d/maintainability)](https://codeclimate.com/github/dallgoot/yaml/maintainability) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/dallgoot/yaml/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/dallgoot/yaml/?branch=master)
+[![Code Coverage](https://scrutinizer-ci.com/g/dallgoot/yaml/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/dallgoot/yaml/?branch=master)
+
+PHP library to load and parse YAML file to coherent PHP datatypes equivalent
+
+![Dallgoot/Yaml Library](dallgoot_yaml.png)
+
+## Installation
+
+- Dependencies are only useful for building documentation or for code contribution, so the "--update-no-dev" prevent from downloading and managing packages that you probably won't use.
+
+You first need [Composer](https://getcomposer.org/) and PHP ^8.1.14
+
+```bash
+composer require --update-no-dev dallgoot/yaml
+```
+
+## Usage
+
+See [examples folder](./examples)
+
+## Features
+
+- *consistent* PHP datatypes :
+ - object for mappings
+ - array for sequences
+ - common scalars : string, integer, float, INF, NAN
+ - JSON, DateTime(option), etc.
+- specific types (objects)
+ - **YamlObject** for each Yaml content (multi-documents YAML is an array of YamlObject)
+ - **Compact** for compact/short YAML syntax
+ - **Tagged** object when tag is not determinable
+- recover from some parsing errors
+- tolerant to tabulations
+- debug levels :
+ - 1 : print each line Node Type class and exit
+ - 2 : print Loader global tree structure and exit
+ - 3 : print each document NodeList and exit
+
+## Support
+
+- YAML specifications [version 1.2](http://yaml.org/spec/1.2/spec.html)
+- multi-line values (simple|double quoted or not, compact mapping|sequence or JSON)
+- multiple documents in a content (file or string)
+- compact syntax for mappings and sequences
+- comments (not yet implemented)
+- references (option : enabled by default)
+- tags with behaviour customization (overriding for common(CoreSchema), or specifying for custom) via implementing Tag/SchemaInterface.
+
+## What's different from other PHP Yaml libraries ?
+
+| | YAML version supported | coherent data types | multiple documents | JSON format validation | complex mapping | real reference behaviour | custom tags handling |
+| -------------------------------------------------------------------- |:----------------------:|:-------------------:|:------------------:|:----------------------:|:---------------:|:------------------------:|:--------------------:|
+| [Symfony Yaml](https://symfony.com/doc/current/components/yaml.html) | 1.2 | ❌ | ❌ | ❌ | ❌ | ❌ | ✔️ |
+| [php-yaml](https://pecl.php.net/package/yaml) | 1.1 | ❌ | ✔️ | ❌ | ❌ | ❌ | ✔️ |
+| [syck](http://pecl.php.net/package/syck) | 1.0 | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
+| [spyc](https://github.com/mustangostang/spyc) | 1.0 | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
+| **Dallgoot/Yaml** | 1.2 | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
+
+- coherent data types (see [coherence.md](./documentation/coherence.md) for explanations)
+- JSON format validation (Option, Note: if valid as per PHP function [json_encode](https://www.php.net/manual/en/function.json-encode.php))
+- complex mapping (Note: keys are JSON formatted strings)
+- real reference behaviour : changing reference value modify other reference calls
+
+## Contributing
+
+ Only contributions concerning bug fixes will be review ATM.
+ Requests for features will be dealt with after reading/writing YAML is considered bug free (and al current Options are implemented)
+
+## ToDo
+
+- Code coverage : target 100%
+- Benchmarks against other libs
+
+## Improvements
+
+- Examples of each function of the API
+- implement specific unit test for each YAML spec. invalid cases (what must not happen)
+- DUMPER:
+ - implement/verify Dumper::Options
+- better/more precise errors identification (Yaml validation) with explanation in YAML content
+- Unicode checking (???)
+- OPTION : parse dates as PHP DateTime object
+- OPTION: Force renaming key names that are not valid PHP property name
+- TAG : function for 'php/object' that provides the correct namespace to build
+- NEON compatibility???
+- make immutable YamlObject
+
+## Performances
+
+ - TBD
+ - improved memory using SplFixedArray instead of regular arrays where possible
+
+
+## Sponsor
+That is greatly appreciated :
+## Thanks
+
+- https://yaml.org
+- https://www.json2yaml.com/convert-yaml-to-json
+- [Symfony Yaml](https://symfony.com/doc/current/components/yaml.html)
diff --git a/classes/vendor/81x/dallgoot/yaml/composer.json b/classes/vendor/81x/dallgoot/yaml/composer.json
new file mode 100644
index 0000000..b54b649
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/composer.json
@@ -0,0 +1,48 @@
+{
+ "name": "dallgoot/yaml",
+ "description": "Provides loader, dumper and an API for YAML content. Loader builds to equivalent data types in PHP 8.x",
+ "type": "library",
+ "keywords": [
+ "yaml",
+ "parser",
+ "reader",
+ "writer"
+ ],
+ "homepage": "https://github.com/dallgoot/yaml",
+ "license": "Apache-2.0",
+ "authors": [
+ {
+ "name": "dallgoot",
+ "email": "stephane.rebai@gmail.com"
+ }
+ ],
+ "autoload": {
+ "psr-4": {
+ "Dallgoot\\Yaml\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Dallgoot\\Yaml\\Tests\\": "tests/"
+ }
+ },
+ "require": {
+ "php": ">=8.1.14",
+ "ext-json": "*",
+ "ext-pcre": "*",
+ "ext-SPL": "*",
+ "lib-pcre": "*"
+ },
+ "require-dev": {
+ "ext-Reflection": "*",
+ "phan/phan": "*",
+ "phpmetrics/phpmetrics": "*",
+ "phpunit/phpunit": "*"
+ },
+ "scripts": {
+ "test": "phpunit -c ./configuration/phpunit.xml --no-coverage --testsuite All",
+ "coverage": "XDEBUG_MODE=coverage phpunit -c ./configuration/phpunit.xml --testsuite All",
+ "metrics": "phpmetrics --report-html=documentation/phpmetrics_report ./src",
+ "phan": "phan -k configuration/phan.php -C -j2 -3src/Tag"
+ }
+}
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/composer.lock b/classes/vendor/81x/dallgoot/yaml/composer.lock
new file mode 100644
index 0000000..b583855
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/composer.lock
@@ -0,0 +1,3429 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "99e92f298d7c52a0955d879093380722",
+ "packages": [],
+ "packages-dev": [
+ {
+ "name": "composer/pcre",
+ "version": "3.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/composer/pcre.git",
+ "reference": "4bff79ddd77851fe3cdd11616ed3f92841ba5bd2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/composer/pcre/zipball/4bff79ddd77851fe3cdd11616ed3f92841ba5bd2",
+ "reference": "4bff79ddd77851fe3cdd11616ed3f92841ba5bd2",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.4 || ^8.0"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^1.3",
+ "phpstan/phpstan-strict-rules": "^1.1",
+ "symfony/phpunit-bridge": "^5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Composer\\Pcre\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jordi Boggiano",
+ "email": "j.boggiano@seld.be",
+ "homepage": "http://seld.be"
+ }
+ ],
+ "description": "PCRE wrapping library that offers type-safe preg_* replacements.",
+ "keywords": [
+ "PCRE",
+ "preg",
+ "regex",
+ "regular expression"
+ ],
+ "support": {
+ "issues": "https://github.com/composer/pcre/issues",
+ "source": "https://github.com/composer/pcre/tree/3.1.0"
+ },
+ "funding": [
+ {
+ "url": "https://packagist.com",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/composer",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/composer/composer",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2022-11-17T09:50:14+00:00"
+ },
+ {
+ "name": "composer/semver",
+ "version": "3.3.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/composer/semver.git",
+ "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/composer/semver/zipball/3953f23262f2bff1919fc82183ad9acb13ff62c9",
+ "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.3.2 || ^7.0 || ^8.0"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^1.4",
+ "symfony/phpunit-bridge": "^4.2 || ^5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Composer\\Semver\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nils Adermann",
+ "email": "naderman@naderman.de",
+ "homepage": "http://www.naderman.de"
+ },
+ {
+ "name": "Jordi Boggiano",
+ "email": "j.boggiano@seld.be",
+ "homepage": "http://seld.be"
+ },
+ {
+ "name": "Rob Bast",
+ "email": "rob.bast@gmail.com",
+ "homepage": "http://robbast.nl"
+ }
+ ],
+ "description": "Semver library that offers utilities, version constraint parsing and validation.",
+ "keywords": [
+ "semantic",
+ "semver",
+ "validation",
+ "versioning"
+ ],
+ "support": {
+ "irc": "irc://irc.freenode.org/composer",
+ "issues": "https://github.com/composer/semver/issues",
+ "source": "https://github.com/composer/semver/tree/3.3.2"
+ },
+ "funding": [
+ {
+ "url": "https://packagist.com",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/composer",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/composer/composer",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2022-04-01T19:23:25+00:00"
+ },
+ {
+ "name": "composer/xdebug-handler",
+ "version": "3.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/composer/xdebug-handler.git",
+ "reference": "ced299686f41dce890debac69273b47ffe98a40c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/ced299686f41dce890debac69273b47ffe98a40c",
+ "reference": "ced299686f41dce890debac69273b47ffe98a40c",
+ "shasum": ""
+ },
+ "require": {
+ "composer/pcre": "^1 || ^2 || ^3",
+ "php": "^7.2.5 || ^8.0",
+ "psr/log": "^1 || ^2 || ^3"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^1.0",
+ "phpstan/phpstan-strict-rules": "^1.1",
+ "symfony/phpunit-bridge": "^6.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Composer\\XdebugHandler\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "John Stevenson",
+ "email": "john-stevenson@blueyonder.co.uk"
+ }
+ ],
+ "description": "Restarts a process without Xdebug.",
+ "keywords": [
+ "Xdebug",
+ "performance"
+ ],
+ "support": {
+ "irc": "irc://irc.freenode.org/composer",
+ "issues": "https://github.com/composer/xdebug-handler/issues",
+ "source": "https://github.com/composer/xdebug-handler/tree/3.0.3"
+ },
+ "funding": [
+ {
+ "url": "https://packagist.com",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/composer",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/composer/composer",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2022-02-25T21:32:43+00:00"
+ },
+ {
+ "name": "doctrine/deprecations",
+ "version": "v1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/deprecations.git",
+ "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/deprecations/zipball/0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de",
+ "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1|^8.0"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^9",
+ "phpunit/phpunit": "^7.5|^8.5|^9.5",
+ "psr/log": "^1|^2|^3"
+ },
+ "suggest": {
+ "psr/log": "Allows logging deprecations via PSR-3 logger implementation"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.",
+ "homepage": "https://www.doctrine-project.org/",
+ "support": {
+ "issues": "https://github.com/doctrine/deprecations/issues",
+ "source": "https://github.com/doctrine/deprecations/tree/v1.0.0"
+ },
+ "time": "2022-05-02T15:47:09+00:00"
+ },
+ {
+ "name": "felixfbecker/advanced-json-rpc",
+ "version": "v3.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/felixfbecker/php-advanced-json-rpc.git",
+ "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/felixfbecker/php-advanced-json-rpc/zipball/b5f37dbff9a8ad360ca341f3240dc1c168b45447",
+ "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447",
+ "shasum": ""
+ },
+ "require": {
+ "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0",
+ "php": "^7.1 || ^8.0",
+ "phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^7.0 || ^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "AdvancedJsonRpc\\": "lib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "ISC"
+ ],
+ "authors": [
+ {
+ "name": "Felix Becker",
+ "email": "felix.b@outlook.com"
+ }
+ ],
+ "description": "A more advanced JSONRPC implementation",
+ "support": {
+ "issues": "https://github.com/felixfbecker/php-advanced-json-rpc/issues",
+ "source": "https://github.com/felixfbecker/php-advanced-json-rpc/tree/v3.2.1"
+ },
+ "time": "2021-06-11T22:34:44+00:00"
+ },
+ {
+ "name": "microsoft/tolerant-php-parser",
+ "version": "v0.1.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/microsoft/tolerant-php-parser.git",
+ "reference": "3eccfd273323aaf69513e2f1c888393f5947804b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/microsoft/tolerant-php-parser/zipball/3eccfd273323aaf69513e2f1c888393f5947804b",
+ "reference": "3eccfd273323aaf69513e2f1c888393f5947804b",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^8.5.15"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Microsoft\\PhpParser\\": [
+ "src/"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Rob Lourens",
+ "email": "roblou@microsoft.com"
+ }
+ ],
+ "description": "Tolerant PHP-to-AST parser designed for IDE usage scenarios",
+ "support": {
+ "issues": "https://github.com/microsoft/tolerant-php-parser/issues",
+ "source": "https://github.com/microsoft/tolerant-php-parser/tree/v0.1.2"
+ },
+ "time": "2022-10-05T17:30:19+00:00"
+ },
+ {
+ "name": "myclabs/deep-copy",
+ "version": "1.11.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/myclabs/DeepCopy.git",
+ "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c",
+ "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1 || ^8.0"
+ },
+ "conflict": {
+ "doctrine/collections": "<1.6.8",
+ "doctrine/common": "<2.13.3 || >=3,<3.2.2"
+ },
+ "require-dev": {
+ "doctrine/collections": "^1.6.8",
+ "doctrine/common": "^2.13.3 || ^3.2.2",
+ "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/DeepCopy/deep_copy.php"
+ ],
+ "psr-4": {
+ "DeepCopy\\": "src/DeepCopy/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Create deep copies (clones) of your objects",
+ "keywords": [
+ "clone",
+ "copy",
+ "duplicate",
+ "object",
+ "object graph"
+ ],
+ "support": {
+ "issues": "https://github.com/myclabs/DeepCopy/issues",
+ "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1"
+ },
+ "funding": [
+ {
+ "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2023-03-08T13:26:56+00:00"
+ },
+ {
+ "name": "netresearch/jsonmapper",
+ "version": "v4.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/cweiske/jsonmapper.git",
+ "reference": "cfa81ea1d35294d64adb9c68aa4cb9e92400e53f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/cfa81ea1d35294d64adb9c68aa4cb9e92400e53f",
+ "reference": "cfa81ea1d35294d64adb9c68aa4cb9e92400e53f",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "ext-pcre": "*",
+ "ext-reflection": "*",
+ "ext-spl": "*",
+ "php": ">=7.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~7.5 || ~8.0 || ~9.0",
+ "squizlabs/php_codesniffer": "~3.5"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "JsonMapper": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "OSL-3.0"
+ ],
+ "authors": [
+ {
+ "name": "Christian Weiske",
+ "email": "cweiske@cweiske.de",
+ "homepage": "http://github.com/cweiske/jsonmapper/",
+ "role": "Developer"
+ }
+ ],
+ "description": "Map nested JSON structures onto PHP classes",
+ "support": {
+ "email": "cweiske@cweiske.de",
+ "issues": "https://github.com/cweiske/jsonmapper/issues",
+ "source": "https://github.com/cweiske/jsonmapper/tree/v4.1.0"
+ },
+ "time": "2022-12-08T20:46:14+00:00"
+ },
+ {
+ "name": "nikic/php-parser",
+ "version": "v4.15.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nikic/PHP-Parser.git",
+ "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6bb5176bc4af8bcb7d926f88718db9b96a2d4290",
+ "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290",
+ "shasum": ""
+ },
+ "require": {
+ "ext-tokenizer": "*",
+ "php": ">=7.0"
+ },
+ "require-dev": {
+ "ircmaxell/php-yacc": "^0.0.7",
+ "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0"
+ },
+ "bin": [
+ "bin/php-parse"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.9-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PhpParser\\": "lib/PhpParser"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Nikita Popov"
+ }
+ ],
+ "description": "A PHP parser written in PHP",
+ "keywords": [
+ "parser",
+ "php"
+ ],
+ "support": {
+ "issues": "https://github.com/nikic/PHP-Parser/issues",
+ "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.4"
+ },
+ "time": "2023-03-05T19:49:14+00:00"
+ },
+ {
+ "name": "phan/phan",
+ "version": "5.4.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phan/phan.git",
+ "reference": "4f2870ed6fea320f62f3c3c63f3274d357a7980e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phan/phan/zipball/4f2870ed6fea320f62f3c3c63f3274d357a7980e",
+ "reference": "4f2870ed6fea320f62f3c3c63f3274d357a7980e",
+ "shasum": ""
+ },
+ "require": {
+ "composer/semver": "^1.4|^2.0|^3.0",
+ "composer/xdebug-handler": "^2.0|^3.0",
+ "ext-filter": "*",
+ "ext-json": "*",
+ "ext-tokenizer": "*",
+ "felixfbecker/advanced-json-rpc": "^3.0.4",
+ "microsoft/tolerant-php-parser": "0.1.2",
+ "netresearch/jsonmapper": "^1.6.0|^2.0|^3.0|^4.0",
+ "php": "^7.2.0|^8.0.0",
+ "sabre/event": "^5.1.3",
+ "symfony/console": "^3.2|^4.0|^5.0|^6.0",
+ "symfony/polyfill-mbstring": "^1.11.0",
+ "symfony/polyfill-php80": "^1.20.0",
+ "tysonandre/var_representation_polyfill": "^0.0.2|^0.1.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^8.5.0"
+ },
+ "suggest": {
+ "ext-ast": "Needed for parsing ASTs (unless --use-fallback-parser is used). 1.0.1+ is needed, 1.0.16+ is recommended.",
+ "ext-iconv": "Either iconv or mbstring is needed to ensure issue messages are valid utf-8",
+ "ext-igbinary": "Improves performance of polyfill when ext-ast is unavailable",
+ "ext-mbstring": "Either iconv or mbstring is needed to ensure issue messages are valid utf-8",
+ "ext-tokenizer": "Needed for fallback/polyfill parser support and file/line-based suppressions.",
+ "ext-var_representation": "Suggested for converting values to strings in issue messages"
+ },
+ "bin": [
+ "phan",
+ "phan_client",
+ "tocheckstyle"
+ ],
+ "type": "project",
+ "autoload": {
+ "psr-4": {
+ "Phan\\": "src/Phan"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Tyson Andre"
+ },
+ {
+ "name": "Rasmus Lerdorf"
+ },
+ {
+ "name": "Andrew S. Morrison"
+ }
+ ],
+ "description": "A static analyzer for PHP",
+ "keywords": [
+ "analyzer",
+ "php",
+ "static"
+ ],
+ "support": {
+ "issues": "https://github.com/phan/phan/issues",
+ "source": "https://github.com/phan/phan/tree/5.4.2"
+ },
+ "time": "2023-03-03T17:20:24+00:00"
+ },
+ {
+ "name": "phar-io/manifest",
+ "version": "2.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/manifest.git",
+ "reference": "97803eca37d319dfa7826cc2437fc020857acb53"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53",
+ "reference": "97803eca37d319dfa7826cc2437fc020857acb53",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-phar": "*",
+ "ext-xmlwriter": "*",
+ "phar-io/version": "^3.0.1",
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
+ "support": {
+ "issues": "https://github.com/phar-io/manifest/issues",
+ "source": "https://github.com/phar-io/manifest/tree/2.0.3"
+ },
+ "time": "2021-07-20T11:28:43+00:00"
+ },
+ {
+ "name": "phar-io/version",
+ "version": "3.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/version.git",
+ "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
+ "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Library for handling version information and constraints",
+ "support": {
+ "issues": "https://github.com/phar-io/version/issues",
+ "source": "https://github.com/phar-io/version/tree/3.2.1"
+ },
+ "time": "2022-02-21T01:04:05+00:00"
+ },
+ {
+ "name": "phpdocumentor/reflection-common",
+ "version": "2.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
+ "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b",
+ "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-2.x": "2.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jaap van Otterdijk",
+ "email": "opensource@ijaap.nl"
+ }
+ ],
+ "description": "Common reflection classes used by phpdocumentor to reflect the code structure",
+ "homepage": "http://www.phpdoc.org",
+ "keywords": [
+ "FQSEN",
+ "phpDocumentor",
+ "phpdoc",
+ "reflection",
+ "static analysis"
+ ],
+ "support": {
+ "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues",
+ "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x"
+ },
+ "time": "2020-06-27T09:03:43+00:00"
+ },
+ {
+ "name": "phpdocumentor/reflection-docblock",
+ "version": "5.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
+ "reference": "622548b623e81ca6d78b721c5e029f4ce664f170"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170",
+ "reference": "622548b623e81ca6d78b721c5e029f4ce664f170",
+ "shasum": ""
+ },
+ "require": {
+ "ext-filter": "*",
+ "php": "^7.2 || ^8.0",
+ "phpdocumentor/reflection-common": "^2.2",
+ "phpdocumentor/type-resolver": "^1.3",
+ "webmozart/assert": "^1.9.1"
+ },
+ "require-dev": {
+ "mockery/mockery": "~1.3.2",
+ "psalm/phar": "^4.8"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Mike van Riel",
+ "email": "me@mikevanriel.com"
+ },
+ {
+ "name": "Jaap van Otterdijk",
+ "email": "account@ijaap.nl"
+ }
+ ],
+ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
+ "support": {
+ "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues",
+ "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0"
+ },
+ "time": "2021-10-19T17:43:47+00:00"
+ },
+ {
+ "name": "phpdocumentor/type-resolver",
+ "version": "1.7.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/TypeResolver.git",
+ "reference": "dfc078e8af9c99210337325ff5aa152872c98714"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/dfc078e8af9c99210337325ff5aa152872c98714",
+ "reference": "dfc078e8af9c99210337325ff5aa152872c98714",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/deprecations": "^1.0",
+ "php": "^7.4 || ^8.0",
+ "phpdocumentor/reflection-common": "^2.0",
+ "phpstan/phpdoc-parser": "^1.13"
+ },
+ "require-dev": {
+ "ext-tokenizer": "*",
+ "phpbench/phpbench": "^1.2",
+ "phpstan/extension-installer": "^1.1",
+ "phpstan/phpstan": "^1.8",
+ "phpstan/phpstan-phpunit": "^1.1",
+ "phpunit/phpunit": "^9.5",
+ "rector/rector": "^0.13.9",
+ "vimeo/psalm": "^4.25"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-1.x": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Mike van Riel",
+ "email": "me@mikevanriel.com"
+ }
+ ],
+ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
+ "support": {
+ "issues": "https://github.com/phpDocumentor/TypeResolver/issues",
+ "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.7.1"
+ },
+ "time": "2023-03-27T19:02:04+00:00"
+ },
+ {
+ "name": "phpmetrics/phpmetrics",
+ "version": "v2.8.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpmetrics/PhpMetrics.git",
+ "reference": "4b77140a11452e63c7a9b98e0648320bf6710090"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpmetrics/PhpMetrics/zipball/4b77140a11452e63c7a9b98e0648320bf6710090",
+ "reference": "4b77140a11452e63c7a9b98e0648320bf6710090",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-tokenizer": "*",
+ "nikic/php-parser": "^3|^4",
+ "php": ">=5.5"
+ },
+ "replace": {
+ "halleck45/php-metrics": "*",
+ "halleck45/phpmetrics": "*"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14",
+ "sebastian/comparator": ">=1.2.3",
+ "squizlabs/php_codesniffer": "^3.5",
+ "symfony/dom-crawler": "^3.0 || ^4.0 || ^5.0"
+ },
+ "bin": [
+ "bin/phpmetrics"
+ ],
+ "type": "library",
+ "autoload": {
+ "files": [
+ "./src/functions.php"
+ ],
+ "psr-0": {
+ "Hal\\": "./src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jean-François Lépine",
+ "email": "lepinejeanfrancois@yahoo.fr",
+ "homepage": "http://www.lepine.pro",
+ "role": "Copyright Holder"
+ }
+ ],
+ "description": "Static analyzer tool for PHP : Coupling, Cyclomatic complexity, Maintainability Index, Halstead's metrics... and more !",
+ "homepage": "http://www.phpmetrics.org",
+ "keywords": [
+ "analysis",
+ "qa",
+ "quality",
+ "testing"
+ ],
+ "support": {
+ "issues": "https://github.com/PhpMetrics/PhpMetrics/issues",
+ "source": "https://github.com/phpmetrics/PhpMetrics/tree/v2.8.2"
+ },
+ "time": "2023-03-08T15:03:36+00:00"
+ },
+ {
+ "name": "phpstan/phpdoc-parser",
+ "version": "1.18.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpstan/phpdoc-parser.git",
+ "reference": "882eabc9b6a12e25c27091a261397f9c8792e722"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/882eabc9b6a12e25c27091a261397f9c8792e722",
+ "reference": "882eabc9b6a12e25c27091a261397f9c8792e722",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "require-dev": {
+ "php-parallel-lint/php-parallel-lint": "^1.2",
+ "phpstan/extension-installer": "^1.0",
+ "phpstan/phpstan": "^1.5",
+ "phpstan/phpstan-phpunit": "^1.1",
+ "phpstan/phpstan-strict-rules": "^1.0",
+ "phpunit/phpunit": "^9.5",
+ "symfony/process": "^5.2"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "PHPStan\\PhpDocParser\\": [
+ "src/"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "PHPDoc parser with support for nullable, intersection and generic types",
+ "support": {
+ "issues": "https://github.com/phpstan/phpdoc-parser/issues",
+ "source": "https://github.com/phpstan/phpdoc-parser/tree/1.18.0"
+ },
+ "time": "2023-04-06T07:26:43+00:00"
+ },
+ {
+ "name": "phpunit/php-code-coverage",
+ "version": "10.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+ "reference": "20800e84296ea4732f9a125e08ce86b4004ae3e4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/20800e84296ea4732f9a125e08ce86b4004ae3e4",
+ "reference": "20800e84296ea4732f9a125e08ce86b4004ae3e4",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-libxml": "*",
+ "ext-xmlwriter": "*",
+ "nikic/php-parser": "^4.15",
+ "php": ">=8.1",
+ "phpunit/php-file-iterator": "^4.0",
+ "phpunit/php-text-template": "^3.0",
+ "sebastian/code-unit-reverse-lookup": "^3.0",
+ "sebastian/complexity": "^3.0",
+ "sebastian/environment": "^6.0",
+ "sebastian/lines-of-code": "^2.0",
+ "sebastian/version": "^4.0",
+ "theseer/tokenizer": "^1.2.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0"
+ },
+ "suggest": {
+ "ext-pcov": "PHP extension that provides line coverage",
+ "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "10.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
+ "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
+ "keywords": [
+ "coverage",
+ "testing",
+ "xunit"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
+ "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-03-06T13:00:19+00:00"
+ },
+ {
+ "name": "phpunit/php-file-iterator",
+ "version": "4.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+ "reference": "fd9329ab3368f59fe1fe808a189c51086bd4b6bd"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/fd9329ab3368f59fe1fe808a189c51086bd4b6bd",
+ "reference": "fd9329ab3368f59fe1fe808a189c51086bd4b6bd",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "FilterIterator implementation that filters files based on a list of suffixes.",
+ "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
+ "keywords": [
+ "filesystem",
+ "iterator"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
+ "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-02-10T16:53:14+00:00"
+ },
+ {
+ "name": "phpunit/php-invoker",
+ "version": "4.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-invoker.git",
+ "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7",
+ "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "ext-pcntl": "*",
+ "phpunit/phpunit": "^10.0"
+ },
+ "suggest": {
+ "ext-pcntl": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Invoke callables with a timeout",
+ "homepage": "https://github.com/sebastianbergmann/php-invoker/",
+ "keywords": [
+ "process"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-invoker/issues",
+ "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-02-03T06:56:09+00:00"
+ },
+ {
+ "name": "phpunit/php-text-template",
+ "version": "3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-text-template.git",
+ "reference": "9f3d3709577a527025f55bcf0f7ab8052c8bb37d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/9f3d3709577a527025f55bcf0f7ab8052c8bb37d",
+ "reference": "9f3d3709577a527025f55bcf0f7ab8052c8bb37d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Simple template engine.",
+ "homepage": "https://github.com/sebastianbergmann/php-text-template/",
+ "keywords": [
+ "template"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-text-template/issues",
+ "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-02-03T06:56:46+00:00"
+ },
+ {
+ "name": "phpunit/php-timer",
+ "version": "6.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-timer.git",
+ "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d",
+ "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Utility class for timing",
+ "homepage": "https://github.com/sebastianbergmann/php-timer/",
+ "keywords": [
+ "timer"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-timer/issues",
+ "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-02-03T06:57:52+00:00"
+ },
+ {
+ "name": "phpunit/phpunit",
+ "version": "10.0.19",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/phpunit.git",
+ "reference": "20c23e85c86e5c06d63538ba464e8054f4744e62"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/20c23e85c86e5c06d63538ba464e8054f4744e62",
+ "reference": "20c23e85c86e5c06d63538ba464e8054f4744e62",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-json": "*",
+ "ext-libxml": "*",
+ "ext-mbstring": "*",
+ "ext-xml": "*",
+ "ext-xmlwriter": "*",
+ "myclabs/deep-copy": "^1.10.1",
+ "phar-io/manifest": "^2.0.3",
+ "phar-io/version": "^3.0.2",
+ "php": ">=8.1",
+ "phpunit/php-code-coverage": "^10.0",
+ "phpunit/php-file-iterator": "^4.0",
+ "phpunit/php-invoker": "^4.0",
+ "phpunit/php-text-template": "^3.0",
+ "phpunit/php-timer": "^6.0",
+ "sebastian/cli-parser": "^2.0",
+ "sebastian/code-unit": "^2.0",
+ "sebastian/comparator": "^5.0",
+ "sebastian/diff": "^5.0",
+ "sebastian/environment": "^6.0",
+ "sebastian/exporter": "^5.0",
+ "sebastian/global-state": "^6.0",
+ "sebastian/object-enumerator": "^5.0",
+ "sebastian/recursion-context": "^5.0",
+ "sebastian/type": "^4.0",
+ "sebastian/version": "^4.0"
+ },
+ "suggest": {
+ "ext-soap": "To be able to generate mocks based on WSDL files"
+ },
+ "bin": [
+ "phpunit"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "10.0-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/Framework/Assert/Functions.php"
+ ],
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "The PHP Unit Testing framework.",
+ "homepage": "https://phpunit.de/",
+ "keywords": [
+ "phpunit",
+ "testing",
+ "xunit"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/phpunit/issues",
+ "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/10.0.19"
+ },
+ "funding": [
+ {
+ "url": "https://phpunit.de/sponsors.html",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2023-03-27T11:46:33+00:00"
+ },
+ {
+ "name": "psr/container",
+ "version": "2.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/container.git",
+ "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+ "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.4.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Container\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common Container Interface (PHP FIG PSR-11)",
+ "homepage": "https://github.com/php-fig/container",
+ "keywords": [
+ "PSR-11",
+ "container",
+ "container-interface",
+ "container-interop",
+ "psr"
+ ],
+ "support": {
+ "issues": "https://github.com/php-fig/container/issues",
+ "source": "https://github.com/php-fig/container/tree/2.0.2"
+ },
+ "time": "2021-11-05T16:47:00+00:00"
+ },
+ {
+ "name": "psr/log",
+ "version": "3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/log.git",
+ "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001",
+ "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.0.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Log\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for logging libraries",
+ "homepage": "https://github.com/php-fig/log",
+ "keywords": [
+ "log",
+ "psr",
+ "psr-3"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/log/tree/3.0.0"
+ },
+ "time": "2021-07-14T16:46:02+00:00"
+ },
+ {
+ "name": "sabre/event",
+ "version": "5.1.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sabre-io/event.git",
+ "reference": "d7da22897125d34d7eddf7977758191c06a74497"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sabre-io/event/zipball/d7da22897125d34d7eddf7977758191c06a74497",
+ "reference": "d7da22897125d34d7eddf7977758191c06a74497",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1 || ^8.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "~2.17.1",
+ "phpstan/phpstan": "^0.12",
+ "phpunit/phpunit": "^7.5 || ^8.5 || ^9.0"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "lib/coroutine.php",
+ "lib/Loop/functions.php",
+ "lib/Promise/functions.php"
+ ],
+ "psr-4": {
+ "Sabre\\Event\\": "lib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Evert Pot",
+ "email": "me@evertpot.com",
+ "homepage": "http://evertpot.com/",
+ "role": "Developer"
+ }
+ ],
+ "description": "sabre/event is a library for lightweight event-based programming",
+ "homepage": "http://sabre.io/event/",
+ "keywords": [
+ "EventEmitter",
+ "async",
+ "coroutine",
+ "eventloop",
+ "events",
+ "hooks",
+ "plugin",
+ "promise",
+ "reactor",
+ "signal"
+ ],
+ "support": {
+ "forum": "https://groups.google.com/group/sabredav-discuss",
+ "issues": "https://github.com/sabre-io/event/issues",
+ "source": "https://github.com/fruux/sabre-event"
+ },
+ "time": "2021-11-04T06:51:17+00:00"
+ },
+ {
+ "name": "sebastian/cli-parser",
+ "version": "2.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/cli-parser.git",
+ "reference": "efdc130dbbbb8ef0b545a994fd811725c5282cae"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/efdc130dbbbb8ef0b545a994fd811725c5282cae",
+ "reference": "efdc130dbbbb8ef0b545a994fd811725c5282cae",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for parsing CLI options",
+ "homepage": "https://github.com/sebastianbergmann/cli-parser",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/cli-parser/issues",
+ "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-02-03T06:58:15+00:00"
+ },
+ {
+ "name": "sebastian/code-unit",
+ "version": "2.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/code-unit.git",
+ "reference": "a81fee9eef0b7a76af11d121767abc44c104e503"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503",
+ "reference": "a81fee9eef0b7a76af11d121767abc44c104e503",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Collection of value objects that represent the PHP code units",
+ "homepage": "https://github.com/sebastianbergmann/code-unit",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/code-unit/issues",
+ "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-02-03T06:58:43+00:00"
+ },
+ {
+ "name": "sebastian/code-unit-reverse-lookup",
+ "version": "3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
+ "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d",
+ "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Looks up which function or method a line of code belongs to",
+ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues",
+ "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-02-03T06:59:15+00:00"
+ },
+ {
+ "name": "sebastian/comparator",
+ "version": "5.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/comparator.git",
+ "reference": "72f01e6586e0caf6af81297897bd112eb7e9627c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/72f01e6586e0caf6af81297897bd112eb7e9627c",
+ "reference": "72f01e6586e0caf6af81297897bd112eb7e9627c",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-mbstring": "*",
+ "php": ">=8.1",
+ "sebastian/diff": "^5.0",
+ "sebastian/exporter": "^5.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@2bepublished.at"
+ }
+ ],
+ "description": "Provides the functionality to compare PHP values for equality",
+ "homepage": "https://github.com/sebastianbergmann/comparator",
+ "keywords": [
+ "comparator",
+ "compare",
+ "equality"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/comparator/issues",
+ "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-02-03T07:07:16+00:00"
+ },
+ {
+ "name": "sebastian/complexity",
+ "version": "3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/complexity.git",
+ "reference": "e67d240970c9dc7ea7b2123a6d520e334dd61dc6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/e67d240970c9dc7ea7b2123a6d520e334dd61dc6",
+ "reference": "e67d240970c9dc7ea7b2123a6d520e334dd61dc6",
+ "shasum": ""
+ },
+ "require": {
+ "nikic/php-parser": "^4.10",
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for calculating the complexity of PHP code units",
+ "homepage": "https://github.com/sebastianbergmann/complexity",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/complexity/issues",
+ "source": "https://github.com/sebastianbergmann/complexity/tree/3.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-02-03T06:59:47+00:00"
+ },
+ {
+ "name": "sebastian/diff",
+ "version": "5.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/diff.git",
+ "reference": "aae9a0a43bff37bd5d8d0311426c87bf36153f02"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/aae9a0a43bff37bd5d8d0311426c87bf36153f02",
+ "reference": "aae9a0a43bff37bd5d8d0311426c87bf36153f02",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0",
+ "symfony/process": "^4.2 || ^5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Kore Nordmann",
+ "email": "mail@kore-nordmann.de"
+ }
+ ],
+ "description": "Diff implementation",
+ "homepage": "https://github.com/sebastianbergmann/diff",
+ "keywords": [
+ "diff",
+ "udiff",
+ "unidiff",
+ "unified diff"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/diff/issues",
+ "security": "https://github.com/sebastianbergmann/diff/security/policy",
+ "source": "https://github.com/sebastianbergmann/diff/tree/5.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-03-23T05:12:41+00:00"
+ },
+ {
+ "name": "sebastian/environment",
+ "version": "6.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/environment.git",
+ "reference": "b6f3694c6386c7959915a0037652e0c40f6f69cc"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/b6f3694c6386c7959915a0037652e0c40f6f69cc",
+ "reference": "b6f3694c6386c7959915a0037652e0c40f6f69cc",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0"
+ },
+ "suggest": {
+ "ext-posix": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides functionality to handle HHVM/PHP environments",
+ "homepage": "https://github.com/sebastianbergmann/environment",
+ "keywords": [
+ "Xdebug",
+ "environment",
+ "hhvm"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/environment/issues",
+ "source": "https://github.com/sebastianbergmann/environment/tree/6.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-02-03T07:03:04+00:00"
+ },
+ {
+ "name": "sebastian/exporter",
+ "version": "5.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/exporter.git",
+ "reference": "f3ec4bf931c0b31e5b413f5b4fc970a7d03338c0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/f3ec4bf931c0b31e5b413f5b4fc970a7d03338c0",
+ "reference": "f3ec4bf931c0b31e5b413f5b4fc970a7d03338c0",
+ "shasum": ""
+ },
+ "require": {
+ "ext-mbstring": "*",
+ "php": ">=8.1",
+ "sebastian/recursion-context": "^5.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@gmail.com"
+ }
+ ],
+ "description": "Provides the functionality to export PHP variables for visualization",
+ "homepage": "https://www.github.com/sebastianbergmann/exporter",
+ "keywords": [
+ "export",
+ "exporter"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/exporter/issues",
+ "source": "https://github.com/sebastianbergmann/exporter/tree/5.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-02-03T07:06:49+00:00"
+ },
+ {
+ "name": "sebastian/global-state",
+ "version": "6.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/global-state.git",
+ "reference": "aab257c712de87b90194febd52e4d184551c2d44"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/aab257c712de87b90194febd52e4d184551c2d44",
+ "reference": "aab257c712de87b90194febd52e4d184551c2d44",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "sebastian/object-reflector": "^3.0",
+ "sebastian/recursion-context": "^5.0"
+ },
+ "require-dev": {
+ "ext-dom": "*",
+ "phpunit/phpunit": "^10.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Snapshotting of global state",
+ "homepage": "http://www.github.com/sebastianbergmann/global-state",
+ "keywords": [
+ "global state"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/global-state/issues",
+ "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-02-03T07:07:38+00:00"
+ },
+ {
+ "name": "sebastian/lines-of-code",
+ "version": "2.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/lines-of-code.git",
+ "reference": "17c4d940ecafb3d15d2cf916f4108f664e28b130"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/17c4d940ecafb3d15d2cf916f4108f664e28b130",
+ "reference": "17c4d940ecafb3d15d2cf916f4108f664e28b130",
+ "shasum": ""
+ },
+ "require": {
+ "nikic/php-parser": "^4.10",
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for counting the lines of code in PHP source code",
+ "homepage": "https://github.com/sebastianbergmann/lines-of-code",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/lines-of-code/issues",
+ "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-02-03T07:08:02+00:00"
+ },
+ {
+ "name": "sebastian/object-enumerator",
+ "version": "5.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-enumerator.git",
+ "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906",
+ "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "sebastian/object-reflector": "^3.0",
+ "sebastian/recursion-context": "^5.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Traverses array structures and object graphs to enumerate all referenced objects",
+ "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/object-enumerator/issues",
+ "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-02-03T07:08:32+00:00"
+ },
+ {
+ "name": "sebastian/object-reflector",
+ "version": "3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-reflector.git",
+ "reference": "24ed13d98130f0e7122df55d06c5c4942a577957"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957",
+ "reference": "24ed13d98130f0e7122df55d06c5c4942a577957",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Allows reflection of object attributes, including inherited and non-public ones",
+ "homepage": "https://github.com/sebastianbergmann/object-reflector/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/object-reflector/issues",
+ "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-02-03T07:06:18+00:00"
+ },
+ {
+ "name": "sebastian/recursion-context",
+ "version": "5.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/recursion-context.git",
+ "reference": "05909fb5bc7df4c52992396d0116aed689f93712"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/05909fb5bc7df4c52992396d0116aed689f93712",
+ "reference": "05909fb5bc7df4c52992396d0116aed689f93712",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ }
+ ],
+ "description": "Provides functionality to recursively process PHP variables",
+ "homepage": "https://github.com/sebastianbergmann/recursion-context",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/recursion-context/issues",
+ "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-02-03T07:05:40+00:00"
+ },
+ {
+ "name": "sebastian/type",
+ "version": "4.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/type.git",
+ "reference": "462699a16464c3944eefc02ebdd77882bd3925bf"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf",
+ "reference": "462699a16464c3944eefc02ebdd77882bd3925bf",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Collection of value objects that represent the types of the PHP type system",
+ "homepage": "https://github.com/sebastianbergmann/type",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/type/issues",
+ "source": "https://github.com/sebastianbergmann/type/tree/4.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-02-03T07:10:45+00:00"
+ },
+ {
+ "name": "sebastian/version",
+ "version": "4.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/version.git",
+ "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17",
+ "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that helps with managing the version number of Git-hosted PHP projects",
+ "homepage": "https://github.com/sebastianbergmann/version",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/version/issues",
+ "source": "https://github.com/sebastianbergmann/version/tree/4.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-02-07T11:34:05+00:00"
+ },
+ {
+ "name": "symfony/console",
+ "version": "v6.2.8",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/console.git",
+ "reference": "3582d68a64a86ec25240aaa521ec8bc2342b369b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/console/zipball/3582d68a64a86ec25240aaa521ec8bc2342b369b",
+ "reference": "3582d68a64a86ec25240aaa521ec8bc2342b369b",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "symfony/deprecation-contracts": "^2.1|^3",
+ "symfony/polyfill-mbstring": "~1.0",
+ "symfony/service-contracts": "^1.1|^2|^3",
+ "symfony/string": "^5.4|^6.0"
+ },
+ "conflict": {
+ "symfony/dependency-injection": "<5.4",
+ "symfony/dotenv": "<5.4",
+ "symfony/event-dispatcher": "<5.4",
+ "symfony/lock": "<5.4",
+ "symfony/process": "<5.4"
+ },
+ "provide": {
+ "psr/log-implementation": "1.0|2.0|3.0"
+ },
+ "require-dev": {
+ "psr/log": "^1|^2|^3",
+ "symfony/config": "^5.4|^6.0",
+ "symfony/dependency-injection": "^5.4|^6.0",
+ "symfony/event-dispatcher": "^5.4|^6.0",
+ "symfony/lock": "^5.4|^6.0",
+ "symfony/process": "^5.4|^6.0",
+ "symfony/var-dumper": "^5.4|^6.0"
+ },
+ "suggest": {
+ "psr/log": "For using the console logger",
+ "symfony/event-dispatcher": "",
+ "symfony/lock": "",
+ "symfony/process": ""
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Console\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Eases the creation of beautiful and testable command line interfaces",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "cli",
+ "command-line",
+ "console",
+ "terminal"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/console/tree/v6.2.8"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2023-03-29T21:42:15+00:00"
+ },
+ {
+ "name": "symfony/deprecation-contracts",
+ "version": "v3.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/deprecation-contracts.git",
+ "reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e",
+ "reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.3-dev"
+ },
+ "thanks": {
+ "name": "symfony/contracts",
+ "url": "https://github.com/symfony/contracts"
+ }
+ },
+ "autoload": {
+ "files": [
+ "function.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "A generic function and convention to trigger deprecation notices",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/deprecation-contracts/tree/v3.2.1"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2023-03-01T10:25:55+00:00"
+ },
+ {
+ "name": "symfony/polyfill-ctype",
+ "version": "v1.27.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-ctype.git",
+ "reference": "5bbc823adecdae860bb64756d639ecfec17b050a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a",
+ "reference": "5bbc823adecdae860bb64756d639ecfec17b050a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "provide": {
+ "ext-ctype": "*"
+ },
+ "suggest": {
+ "ext-ctype": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.27-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Ctype\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Gert de Pagter",
+ "email": "BackEndTea@gmail.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for ctype functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "ctype",
+ "polyfill",
+ "portable"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2022-11-03T14:55:06+00:00"
+ },
+ {
+ "name": "symfony/polyfill-intl-grapheme",
+ "version": "v1.27.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-intl-grapheme.git",
+ "reference": "511a08c03c1960e08a883f4cffcacd219b758354"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/511a08c03c1960e08a883f4cffcacd219b758354",
+ "reference": "511a08c03c1960e08a883f4cffcacd219b758354",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "suggest": {
+ "ext-intl": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.27-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Intl\\Grapheme\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for intl's grapheme_* functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "grapheme",
+ "intl",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.27.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2022-11-03T14:55:06+00:00"
+ },
+ {
+ "name": "symfony/polyfill-intl-normalizer",
+ "version": "v1.27.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-intl-normalizer.git",
+ "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6",
+ "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "suggest": {
+ "ext-intl": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.27-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Intl\\Normalizer\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for intl's Normalizer class and related functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "intl",
+ "normalizer",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2022-11-03T14:55:06+00:00"
+ },
+ {
+ "name": "symfony/polyfill-mbstring",
+ "version": "v1.27.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-mbstring.git",
+ "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
+ "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "provide": {
+ "ext-mbstring": "*"
+ },
+ "suggest": {
+ "ext-mbstring": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.27-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Mbstring\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for the Mbstring extension",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "mbstring",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2022-11-03T14:55:06+00:00"
+ },
+ {
+ "name": "symfony/polyfill-php80",
+ "version": "v1.27.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php80.git",
+ "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936",
+ "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.27-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Php80\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ion Bazan",
+ "email": "ion.bazan@gmail.com"
+ },
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2022-11-03T14:55:06+00:00"
+ },
+ {
+ "name": "symfony/service-contracts",
+ "version": "v3.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/service-contracts.git",
+ "reference": "a8c9cedf55f314f3a186041d19537303766df09a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/service-contracts/zipball/a8c9cedf55f314f3a186041d19537303766df09a",
+ "reference": "a8c9cedf55f314f3a186041d19537303766df09a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "psr/container": "^2.0"
+ },
+ "conflict": {
+ "ext-psr": "<1.1|>=2"
+ },
+ "suggest": {
+ "symfony/service-implementation": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.3-dev"
+ },
+ "thanks": {
+ "name": "symfony/contracts",
+ "url": "https://github.com/symfony/contracts"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\Service\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Test/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Generic abstractions related to writing services",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "abstractions",
+ "contracts",
+ "decoupling",
+ "interfaces",
+ "interoperability",
+ "standards"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/service-contracts/tree/v3.2.1"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2023-03-01T10:32:47+00:00"
+ },
+ {
+ "name": "symfony/string",
+ "version": "v6.2.8",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/string.git",
+ "reference": "193e83bbd6617d6b2151c37fff10fa7168ebddef"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/string/zipball/193e83bbd6617d6b2151c37fff10fa7168ebddef",
+ "reference": "193e83bbd6617d6b2151c37fff10fa7168ebddef",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "symfony/polyfill-ctype": "~1.8",
+ "symfony/polyfill-intl-grapheme": "~1.0",
+ "symfony/polyfill-intl-normalizer": "~1.0",
+ "symfony/polyfill-mbstring": "~1.0"
+ },
+ "conflict": {
+ "symfony/translation-contracts": "<2.0"
+ },
+ "require-dev": {
+ "symfony/error-handler": "^5.4|^6.0",
+ "symfony/http-client": "^5.4|^6.0",
+ "symfony/intl": "^6.2",
+ "symfony/translation-contracts": "^2.0|^3.0",
+ "symfony/var-exporter": "^5.4|^6.0"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "Resources/functions.php"
+ ],
+ "psr-4": {
+ "Symfony\\Component\\String\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "grapheme",
+ "i18n",
+ "string",
+ "unicode",
+ "utf-8",
+ "utf8"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/string/tree/v6.2.8"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2023-03-20T16:06:02+00:00"
+ },
+ {
+ "name": "theseer/tokenizer",
+ "version": "1.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/theseer/tokenizer.git",
+ "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e",
+ "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-tokenizer": "*",
+ "ext-xmlwriter": "*",
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
+ "support": {
+ "issues": "https://github.com/theseer/tokenizer/issues",
+ "source": "https://github.com/theseer/tokenizer/tree/1.2.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/theseer",
+ "type": "github"
+ }
+ ],
+ "time": "2021-07-28T10:34:58+00:00"
+ },
+ {
+ "name": "tysonandre/var_representation_polyfill",
+ "version": "0.1.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/TysonAndre/var_representation_polyfill.git",
+ "reference": "e9116c2c352bb0835ca428b442dde7767c11ad32"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/TysonAndre/var_representation_polyfill/zipball/e9116c2c352bb0835ca428b442dde7767c11ad32",
+ "reference": "e9116c2c352bb0835ca428b442dde7767c11ad32",
+ "shasum": ""
+ },
+ "require": {
+ "ext-tokenizer": "*",
+ "php": "^7.2.0|^8.0.0"
+ },
+ "provide": {
+ "ext-var_representation": "*"
+ },
+ "require-dev": {
+ "phan/phan": "^5.4.1",
+ "phpunit/phpunit": "^8.5.0"
+ },
+ "suggest": {
+ "ext-var_representation": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "0.1.3-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/var_representation.php"
+ ],
+ "psr-4": {
+ "VarRepresentation\\": "src/VarRepresentation"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Tyson Andre"
+ }
+ ],
+ "description": "Polyfill for var_representation: convert a variable to a string in a way that fixes the shortcomings of var_export",
+ "keywords": [
+ "var_export",
+ "var_representation"
+ ],
+ "support": {
+ "issues": "https://github.com/TysonAndre/var_representation_polyfill/issues",
+ "source": "https://github.com/TysonAndre/var_representation_polyfill/tree/0.1.3"
+ },
+ "time": "2022-08-31T12:59:22+00:00"
+ },
+ {
+ "name": "webmozart/assert",
+ "version": "1.11.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/webmozarts/assert.git",
+ "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991",
+ "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991",
+ "shasum": ""
+ },
+ "require": {
+ "ext-ctype": "*",
+ "php": "^7.2 || ^8.0"
+ },
+ "conflict": {
+ "phpstan/phpstan": "<0.12.20",
+ "vimeo/psalm": "<4.6.1 || 4.6.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^8.5.13"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.10-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Webmozart\\Assert\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@gmail.com"
+ }
+ ],
+ "description": "Assertions to validate method input/output with nice error messages.",
+ "keywords": [
+ "assert",
+ "check",
+ "validate"
+ ],
+ "support": {
+ "issues": "https://github.com/webmozarts/assert/issues",
+ "source": "https://github.com/webmozarts/assert/tree/1.11.0"
+ },
+ "time": "2022-06-03T18:03:27+00:00"
+ }
+ ],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": [],
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": {
+ "php": ">=8.0.0",
+ "ext-json": "*",
+ "ext-pcre": "*",
+ "ext-spl": "*",
+ "lib-pcre": "*"
+ },
+ "platform-dev": {
+ "ext-reflection": "*"
+ },
+ "plugin-api-version": "2.3.0"
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/documentation/coherence.md b/classes/vendor/81x/dallgoot/yaml/documentation/coherence.md
new file mode 100644
index 0000000..0512700
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/documentation/coherence.md
@@ -0,0 +1,16 @@
+# Sometimes the handling of dataypes in other libraries can be confusing
+
+## Example from Symfony Yaml
+```php
+$object = new \stdClass();
+$object->foo = 'bar';
+
+$dumped = Yaml::dump(['data' => $object], 2, 4, Yaml::DUMP_OBJECT_AS_MAP);
+// $dumped = "data:\n foo: bar"
+```
+
+## Dallgoot\Yaml removes this ambiguity by matching a specific PHP data types with its YAML counterpart like so :
+- PHP standard objects -> YAML mapping
+- PHP standard array -> YAML sequence
+- _Dallgoot\Types\Compact_ -> YAML mapping or sequence according to content
+- _Dallgoot\Types\YamlObject_ -> YAML mapping or sequence according to content
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/examples/batch_read.php b/classes/vendor/81x/dallgoot/yaml/examples/batch_read.php
new file mode 100644
index 0000000..2ea9165
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/examples/batch_read.php
@@ -0,0 +1,19 @@
+ $fileName)
+{
+ $yamlObjList[] = $yloader->load($fileName)->parse();
+}
+
+print_r($yamlObjList);
diff --git a/classes/vendor/81x/dallgoot/yaml/examples/compact_notation.php b/classes/vendor/81x/dallgoot/yaml/examples/compact_notation.php
new file mode 100644
index 0000000..039849e
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/examples/compact_notation.php
@@ -0,0 +1,23 @@
+compact_object->c);
+print_r($obj->compact_array[3]);
+
+//modifying those same values
+$obj->compact_object->c = 3;
+$obj->compact_array[3] = 3;
+
+//printing the corresponding YAML
+print_r(Yaml::dump($obj));
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/examples/config.yml b/classes/vendor/81x/dallgoot/yaml/examples/config.yml
new file mode 100644
index 0000000..34588f8
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/examples/config.yml
@@ -0,0 +1,80 @@
+imports:
+ - { resource: parameters.yml }
+ - { resource: security.yml }
+ - { resource: services.yml }
+
+# Put parameters here that don't need to change on each machine where the app is deployed
+# http://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
+parameters:
+ locale: en
+
+framework:
+ #esi: ~
+ translator: { fallbacks: ["%locale%"] }
+ secret: "%secret%"
+ router:
+ resource: "%kernel.root_dir%/config/routing.yml"
+ strict_requirements: ~
+ form: ~
+ csrf_protection: ~
+ validation: { enable_annotations: true }
+ #serializer: { enable_annotations: true }
+ templating:
+ engines: ['twig']
+ #assets_version: SomeVersionScheme
+ default_locale: "%locale%"
+ trusted_hosts: ~
+ trusted_proxies: ~
+ session:
+ # handler_id set to null will use default session handler from php.ini
+ handler_id: ~
+ fragments: ~
+ http_method_override: true
+
+# Twig Configuration
+twig:
+ debug: "%kernel.debug%"
+ strict_variables: "%kernel.debug%"
+
+# Assetic Configuration
+assetic:
+ debug: "%kernel.debug%"
+ use_controller: false
+ bundles: [ ]
+ #java: /usr/bin/java
+ filters:
+ cssrewrite: ~
+ #closure:
+ # jar: "%kernel.root_dir%/Resources/java/compiler.jar"
+ #yui_css:
+ # jar: "%kernel.root_dir%/Resources/java/yuicompressor-2.4.7.jar"
+
+# Doctrine Configuration
+doctrine:
+ dbal:
+ driver: pdo_mysql
+ host: "%database_host%"
+ port: "%database_port%"
+ dbname: "%database_name%"
+ user: "%database_user%"
+ password: "%database_password%"
+ charset: UTF8
+ # if using pdo_sqlite as your database driver:
+ # 1. add the path in parameters.yml
+ # e.g. database_path: "%kernel.root_dir%/data/data.db3"
+ # 2. Uncomment database_path in parameters.yml.dist
+ # 3. Uncomment next line:
+ # path: "%database_path%"
+
+ orm:
+ auto_generate_proxy_classes: "%kernel.debug%"
+ naming_strategy: doctrine.orm.naming_strategy.underscore
+ auto_mapping: true
+
+# Swiftmailer Configuration
+swiftmailer:
+ transport: "%mailer_transport%"
+ host: "%mailer_host%"
+ username: "%mailer_user%"
+ password: "%mailer_password%"
+ spool: { type: memory }
diff --git a/classes/vendor/81x/dallgoot/yaml/examples/dummy.yml b/classes/vendor/81x/dallgoot/yaml/examples/dummy.yml
new file mode 100644
index 0000000..bada706
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/examples/dummy.yml
@@ -0,0 +1,52 @@
+---
+# <- yaml supports comments, json does not
+# { foo: 'bar' }
+keyempty:
+keysimple: ttt
+keyquoted: 'a'
+keydquoted: "b"
+keyref: &b bh
+keyrefcall: *b
+keyjsonarray: [1,2]
+keyjsonobj: {"a": 2,"b": 3}
+subkey:
+ keypartial: "
+4\"
+fakekey: t
+"
+object:
+ key: 4
+ array:
+ - 324
+ - null
+ - boo lean: &a reference_called
+ - integer: 1
+emptyparagraph: >
+paragraph: >
+ false: qsd
+ Blank lines denote
+
+ paragraph breaks
+ but not linefeed
+emptycontent: |-
+content:
+|-
+ Or we
+ can auto
+ convert line breaks
+ to save space
+array2:
+ - *a #comm
+ - *a
+ - mul:
+ aqdqsdqsdq
+ bqsdqsdqd
+arr:
+ - another: value
+ interitem: 456
+ - lad: "fqqs\"
+ 5654"
+partial:
+ r: |-
+ "{a: 45}"
+ stupdtxt
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/examples/load_modify_save.php b/classes/vendor/81x/dallgoot/yaml/examples/load_modify_save.php
new file mode 100644
index 0000000..b999d07
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/examples/load_modify_save.php
@@ -0,0 +1,11 @@
+object->array[3]->integer = '123456789';
+//dumping the corresponding YAML
+print_r(Yaml::dump($yamlObject, 0));
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/examples/read.php b/classes/vendor/81x/dallgoot/yaml/examples/read.php
new file mode 100644
index 0000000..13dacae
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/examples/read.php
@@ -0,0 +1,26 @@
+object->array[0]);
+
+$yamlContent = <<mapping->somekey->array[0]);
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/examples/references.php b/classes/vendor/81x/dallgoot/yaml/examples/references.php
new file mode 100644
index 0000000..caa56d9
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/examples/references.php
@@ -0,0 +1,43 @@
+anchor_definition);
+print_r($obj->anchor_call);
+
+echo "\nchange anchor/reference value to 123\n";
+$obj->addReference('anchor_name', 123);
+
+print_r($obj->anchor_definition);
+print_r($obj->anchor_call);
+
+echo "\nchange one anchor to new value 'abc'\n";
+$obj->anchor_definition = 'abc';
+
+print_r($obj->anchor_definition);
+print_r($obj->anchor_call);
+
+echo "\nunset anchor_call and re-set value\n";
+unset($obj->anchor_call);
+$obj->anchor_call = 'xyz';
+
+print_r($obj->anchor_definition);
+print_r($obj->anchor_call);
+
+echo "\nchange anchor/reference value to 789\n";
+$obj->addReference('anchor_name', 789);
+
+print_r($obj->anchor_definition);
+print_r($obj->anchor_call);
+
+print_r($obj);
\ No newline at end of file
diff --git a/classes/vendor/dallgoot/yaml/configuration/placeholder.txt b/classes/vendor/81x/dallgoot/yaml/examples/tags.php
similarity index 100%
rename from classes/vendor/dallgoot/yaml/configuration/placeholder.txt
rename to classes/vendor/81x/dallgoot/yaml/examples/tags.php
diff --git a/classes/vendor/81x/dallgoot/yaml/examples/testing.php b/classes/vendor/81x/dallgoot/yaml/examples/testing.php
new file mode 100644
index 0000000..5ea9c7d
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/examples/testing.php
@@ -0,0 +1,24 @@
+
+ * @license Apache 2.0
+ * @link https://github.com/dallgoot/yaml
+ */
+class Builder
+{
+ public bool $dateAsObject = false;
+
+ private int $_options;
+ private int $_debug = 0;
+
+ const INVALID_DOCUMENT = "DOCUMENT %d is invalid,";
+
+ public function __construct($options, $debug)
+ {
+ $this->_options = $options;
+ $this->_debug = $debug;
+ }
+ /**
+ * Builds a YAM content. check multiple documents & split if more than one documents
+ *
+ * @param Root $root The NodeRoot node
+ *
+ * @return array|YamlObject|null list of documents or just one, null if appropriate debug lvl
+ */
+ public function buildContent(Root $root)
+ {
+ switch ($this->_debug) {
+ case 2 : print_r($root);
+ case 1 : return null;
+ }
+ $documents = [];
+ $buffer = new NodeList();
+ try {
+ foreach ($root->value as $child) {
+ if ($child instanceof DocEnd && $child !== $root->value->top()) {
+ $this->pushAndSave($child, $buffer, $documents);
+ } elseif ($child instanceof DocStart) {
+ $this->saveAndPush($child, $buffer, $documents);
+ } else {
+ $buffer->push($child);
+ }
+ }
+ $documents[] = $this->buildDocument($buffer, count($documents) + 1);
+ } catch (\Throwable $e) {
+ throw new \Exception($e->getMessage(), 1, $e);
+ }
+ return count($documents) === 1 ? $documents[0] : $documents;
+ }
+
+ /**
+ * Builds the tree of Node (NodeList) for this document
+ *
+ * @param NodeList $list the list of nodes that constitutes the current document
+ * @param int $docNum the index (starts @ 0) of this document in the whole YAML content provided to $this->buildContent
+ *
+ * @return YamlObject the YAML document as an object
+ */
+ public function buildDocument(NodeList &$list, int $docNum): YamlObject
+ {
+ $yamlObject = new YamlObject($this->_options);
+ $rootNode = new Root();
+ $list->setIteratorMode(NodeList::IT_MODE_DELETE);
+ try {
+ foreach ($list as $child) {
+ $rootNode->add($child);
+ }
+ if ($this->_debug === 3) {
+ echo "Document #$docNum\n";
+ print_r($rootNode);
+ }
+ return $rootNode->build($yamlObject);
+ } catch (\Throwable $e) {
+ throw new \ParseError(sprintf(self::INVALID_DOCUMENT, $docNum) . ':' . $e->getMessage(), 2, $e);
+ }
+ }
+
+ public function pushAndSave(DocEnd $child, NodeList &$buffer, array &$documents)
+ {
+ $buffer->push($child);
+ $documents[] = $this->buildDocument($buffer, count($documents) + 1);
+ $buffer = new NodeList();
+ }
+
+ public function saveAndPush(DocStart $child, NodeList &$buffer, array &$documents)
+ {
+ if ($buffer->count() > 0 && $buffer->hasContent()) {
+ $documents[] = $this->buildDocument($buffer, count($documents) + 1);
+ $buffer = new NodeList($child);
+ } else {
+ $buffer->push($child);
+ }
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/src/Dumper.php b/classes/vendor/81x/dallgoot/yaml/src/Dumper.php
new file mode 100644
index 0000000..bc2b9ea
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/src/Dumper.php
@@ -0,0 +1,149 @@
+
+ * @license Apache 2.0
+ * @link https://github.com/dallgoot/yaml
+ */
+class Dumper
+{
+ public const INDENT = 2;
+ // private const WIDTH = 120; //TODO forget this feature for the moment
+ private const OPTIONS = 0b00000;
+ public const DATE_FORMAT = 'Y-m-d';
+
+ public $options;
+ //options
+ public const EXPAND_SHORT = 0b00001;
+ public const SERIALIZE_CUSTOM_OBJECTS = 0b00010;
+ public const USE_TILDE_AS_NULL = 0b00100;
+
+ public int $floatPrecision = 4;
+
+ private bool $multipleDocs = false;
+
+ public bool $_compactMode = false;
+
+ private ?DumperHandlers $handler;
+
+ public const KEY_MASK_SEQ = '- %s';
+ public const KEY_MASK_MAP = '%s: %s';
+
+ public function __construct($options = null)
+ {
+ $this->options = is_int($options) ? $options : self::OPTIONS;
+ $this->handler = new DumperHandlers($this);
+ }
+ /**
+ * Returns (as a string) the YAML representation of the $dataType provided
+ *
+ * @param mixed $dataType The data type
+ *
+ * @throws \Exception datatype cannot be null
+ *
+ * @return string The Yaml string content
+ */
+ public function toString($dataType, ?int $options = null): string
+ {
+ if (empty($dataType)) throw new \Exception(self::class . ": No content to convert to Yaml");
+ if (is_scalar($dataType)) {
+ return "--- " . $this->handler->dumpScalar($dataType) . PHP_EOL;
+ }
+ return $this->dump($dataType, 0, false, true);
+ }
+
+ /**
+ * Calls and saves the result of Dumper::toString to the file $filePath provided
+ *
+ * @param mixed $dataType The data type
+ *
+ * @throws \Exception datatype cannot be null
+ *
+ * @return bool true = if the file has been correctly saved ( return value from 'file_put_contents')
+ */
+ public function toFile(string $filePath, $dataType, ?int $options = null): bool
+ {
+ return !is_bool(file_put_contents($filePath, $this->toString($dataType, $options)));
+ }
+
+
+
+ public function dump(mixed $dataType, int $indent, bool $isCompact = false, $isRoot = false): string
+ {
+ return match(true) {
+ $dataType instanceof YamlObject => $this->dumpYamlObject($dataType),
+ is_null($dataType) => $this->options & self::USE_TILDE_AS_NULL ? '~' : '',
+ is_scalar($dataType) => $this->handler->dumpScalar($dataType),
+ is_array($dataType) => $this->handler->dumpArray($dataType, $indent, $isCompact, $isRoot),
+ is_object($dataType) => $this->handler->dumpObject($dataType, $indent, $isCompact, $isRoot),
+ is_resource($dataType) => get_resource_type($dataType),
+ is_callable($dataType, false, $callable_name) => $callable_name,
+ default => '[Unknown Type]',
+ };
+ }
+
+
+ public function dumpMultiDoc(array $arrayOfYamlObject): string
+ {
+ $docs = [];
+ foreach ($arrayOfYamlObject as $yamlObject) {
+ $docs[] = $this->dumpYamlObject($yamlObject);
+ }
+ return "---\n" . implode("\n---\n", $docs);
+ }
+
+ /**
+ * Dumps an yaml object to a YAML string
+ *
+ * @todo export comment from YamlObject
+ */
+ public function dumpYamlObject(YamlObject $obj): string
+ {
+ if ($this->multipleDocs || $obj->hasDocStart() || $obj->isTagged()) {
+ $this->multipleDocs = true;
+ // && $this->$result instanceof DLL) $this->$result->push("---");
+ }
+ // $this->insertComments($obj->getComment());
+ $properties = get_object_vars($obj);
+ $pairs = [];
+ if (count($properties) === 0) {
+ return $this->handler->dumpArray($obj->getArrayCopy(), 0, false, true);
+ }else {
+ return $this->handler->dumpObject($obj, 0, false, true);
+ }
+ }
+
+
+ // public function iteratorToString(
+ // $compound,
+ // string $keyMask,
+ // string $itemSeparator,
+ // int $indent,
+ // bool $compact = false
+ // ): string {
+ // $pairs = [];
+ // $valueIndent = $indent + self::INDENT;
+ // if(is_object($compound)) {
+ // $compound = get_object_vars($compound);
+ // }
+ // foreach ($compound as $key => $value) {
+ // $separator = "\n";
+ // if (is_scalar($value) || $value instanceof \DateTime) {
+ // $separator = ' ';
+ // $valueIndent = 0;
+ // }
+ // if ($compact) {
+ // $pairs[] = sprintf($keyMask, $key) . $this->dump($value, $valueIndent);
+ // } else {
+ // $pairs[] = str_repeat(' ', $indent) . sprintf($keyMask, $key) . $separator . $this->dump($value, $valueIndent);
+ // }
+ // }
+ // return implode($itemSeparator, $pairs);
+ // }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/src/DumperHandlers.php b/classes/vendor/81x/dallgoot/yaml/src/DumperHandlers.php
new file mode 100644
index 0000000..428278e
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/src/DumperHandlers.php
@@ -0,0 +1,195 @@
+
+ * @license Apache 2.0
+ * @link https://github.com/dallgoot/yaml
+ */
+class DumperHandlers
+{
+ private $dumper;
+
+ public function __construct(Dumper $dumper)
+ {
+ $this->dumper = $dumper;
+ }
+
+
+ public function dumpScalar($dataType): string
+ {
+ if ($dataType === \INF) return '.inf';
+ if ($dataType === -\INF) return '-.inf';
+ $precision = "%." . $this->dumper->floatPrecision . "F";
+ switch (gettype($dataType)) {
+ case 'boolean':
+ return $dataType ? 'true' : 'false';
+ case 'float': //fall through
+ case 'double':
+ return is_nan((float) $dataType) ? '.nan' : sprintf($precision, $dataType);
+ }
+ return $this->dumpString($dataType);
+ }
+
+
+ // public function dumpCompound($compound, int $indent, bool $compact=false): string
+ // {
+ // if ($compact) {
+ // return $this->dumpCompact($compound, $indent);
+ // } else {
+ // if (is_array($compound)) {
+ // if (isset($compound[0]) && $compound[0] instanceof YamlObject) {
+ // return $this->dumper->dumpMultiDoc($compound);
+ // }
+ // $keyMask = '-';
+ // $refKeys = range(0, count($compound) - 1);
+ // if (array_keys($compound) !== $refKeys) {
+ // $keyMask = '%s:';
+ // }
+ // return $this->dumper->iteratorToString($compound, $keyMask, "\n", $indent);
+ // } elseif (is_object($compound) && !is_callable($compound)) {
+ // return $this->dumpObject($compound, $indent);
+ // }
+ // }
+ // throw new \Exception("Dumping Callable|Resource is not currently supported", 1);
+ // }
+
+ public function dumpObject(object $object, int $indent, bool $isCompact = false, bool $isRoot = false): string
+ {
+ return match ($object::class) {
+ Compact::class => $this->dumpCompact($object, $indent),
+ Tagged::class => $this->dumpTagged($object, $indent),
+ \DateTime::class => $this->dumpDateTime($object),
+ default => $this->_object($object, $indent, $isCompact, $isRoot),
+ };
+ }
+
+
+ public function dumpCompact(Compact $compact, int $indent, bool $isRoot = false)
+ {
+ $arr = $compact->getArrayCopy();
+ if (count(get_object_vars($compact)) === 0) {
+ return $this->dumpArray($arr, $indent, true);
+ }
+ return $this->_objectCompact($compact, $indent, $isRoot);
+ }
+
+
+ public function _object(object $o, int $indent, bool $isCompact = false, $isRoot = false): string
+ {
+ return $isCompact ? $this->_objectCompact($o, $indent, $isRoot)
+ : $this->_objectStd($o, $indent, false, $isRoot);
+ }
+
+
+ public function _objectStd(object $o, int $indent, bool $isCompact = false, bool $isRoot = false)
+ {
+ $pairs = [''];
+ $realIndent = $indent + Dumper::INDENT;
+ if($isRoot) {
+ $pairs = [];
+ $realIndent = 0;
+ }
+ foreach (get_object_vars($o) as $key => $value) {
+ $dumpedValue = $this->dumper->dump($value, $realIndent, $value instanceof Compact, false);
+ $pairs[] = sprintf("%s%s: %s", str_repeat(' ', $realIndent), $key, $dumpedValue);
+ }
+ return implode(PHP_EOL, $pairs);
+ }
+
+
+ public function _objectCompact(object $o, int $indent, bool $isRoot = false)
+ {
+ $pairs = [];
+ foreach ($o as $key => $value) {
+ $pairs[] = "$key: " . $this->dumper->dump($value, 0, true, false);
+ }
+ return '{'. implode(', ', $pairs) . '}';
+ }
+
+
+ public function dumpArray(array $a, int $indent, bool $isCompact = false, $isRoot = false): string
+ {
+ if(isset($a[0]) && $a[0] instanceof YamlObject) {
+ return $this->dumper->dumpMultiDoc($a);
+ }
+ if (array_keys($a) !== range(0, count($a) - 1)) {
+ return $this->_object((object) $a, $indent, $isCompact, $isRoot);
+ }
+ return $isCompact ? $this->_dumpCompactArray($a, $indent)
+ : $this->_dumpNativeArray($a, $indent, $isRoot);
+ }
+
+
+ public function _dumpNativeArray(array $a, int $indent, $isRoot = false): string
+ {
+ $pairs = [''];
+ $realIndent = $indent + Dumper::INDENT;
+ if($isRoot) {
+ $pairs = [];
+ $realIndent = 0;
+ }
+ foreach ($a as $value) {
+ $dumpedValue = $this->dumper->dump($value, 0, $value instanceof Compact, false);
+ $pairs[] = sprintf("%s- %s", str_repeat(' ', $realIndent), $dumpedValue);
+ }
+ return implode(PHP_EOL, $pairs);
+ }
+
+
+ public function _dumpCompactArray(array $a, int $indent): string
+ {
+ $pairs = [];
+ foreach ($a as $value) {
+ $pairs[] = $this->dumper->dump($value, $indent, true);
+ }
+ return '[' . implode(', ', $pairs) . ']';
+ }
+
+
+ public function dumpDateTime(\DateTime $datetime): string
+ {
+ return $datetime->format($this->dumper::DATE_FORMAT);
+ }
+
+
+ /**
+ * Dumps a string. Protects it if needed
+ *
+ * @param string $str The string
+ *
+ * @return string ( description_of_the_return_value )
+ * @todo implements checking and protection function
+ */
+ public function dumpString(string $str): string
+ {
+ //those characters must be escaped : - : ? { } [ ] # , & * ! > | ' " %
+ // The “@” (#x40, at) and “`” (#x60, grave accent) are reserved for future use.
+ // 5.4. Line Break Characters
+ // Example 5.13. Escaped Characters
+
+ $str = json_encode(ltrim($str));
+ return strspn(substr($str, 1, -1), "-:?{}[]#,&*!>|'\"%") > 0 ? $str : trim($str, '"');
+ }
+
+ //TODO : handle 'php/object'
+ public function dumpTagged(Tagged $obj, int $indent): string
+ {
+ $separator = "\n";
+ $isCompact = $obj->value instanceof Compact;
+ if (is_scalar($obj->value) || $isCompact) {
+ $separator = ' ';
+ }
+ return ($obj->tagName) . $separator . $this->dumper->dump($obj->value, $indent, $isCompact);
+ }
+
+
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/src/Loader.php b/classes/vendor/81x/dallgoot/yaml/src/Loader.php
new file mode 100644
index 0000000..f9d6aee
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/src/Loader.php
@@ -0,0 +1,193 @@
+
+ * @license Apache 2.0
+ * @link https://github.com/dallgoot/yaml
+ */
+final class Loader
+{
+ //public
+ public static ?string $error;
+ public const IGNORE_DIRECTIVES = 0b0001; //DONT include_directive
+ public const IGNORE_COMMENTS = 0b0010; //DONT include_comments
+ public const NO_PARSING_EXCEPTIONS = 0b0100; //DONT throw Exception on parsing errors
+ public const NO_OBJECT_FOR_DATE = 0b1000; //DONT import date strings as dateTime Object
+
+ //private
+ private ?\SplFixedArray $content = null;
+ private ?string $filePath = null;
+ private int $_debug = 0;
+ private int $_options = 0;
+ private array $_blankBuffer = [];
+
+ //Exceptions messages
+ private const INVALID_VALUE = self::class . ": at line %d";
+ private const EXCEPTION_NO_FILE = self::class . ": file '%s' does not exists (or path is incorrect?)";
+ private const EXCEPTION_READ_ERROR = self::class . ": file '%s' failed to be loaded (permission denied ?)";
+ private const EXCEPTION_LINE_SPLIT = self::class . ": content is not a string (maybe a file error?)";
+
+ /**
+ * Loader constructor
+ */
+ public function __construct(?string $absolutePath = null, ?int $options = null, ?int $debug = 0)
+ {
+ $this->_debug = is_null($debug) ? 0 : min($debug, 3);
+ $this->_options = is_int($options) ? $options : $this->_options;
+ if (is_string($absolutePath)) {
+ $this->load($absolutePath);
+ }
+ }
+
+ /**
+ * Load a file and save its content as $content
+ *
+ * @param string $absolutePath The absolute path of a file
+ *
+ * @throws \Exception if file don't exist OR reading failed
+ *
+ * @return self ( returns the same Loader )
+ */
+ public function load(string $absolutePath): Loader
+ {
+ if (!file_exists($absolutePath)) {
+ throw new \Exception(sprintf(self::EXCEPTION_NO_FILE, $absolutePath));
+ }
+ $this->filePath = $absolutePath;
+
+ $content = @file($absolutePath, FILE_IGNORE_NEW_LINES);
+
+ if (is_bool($content)) {
+ throw new \Exception(sprintf(self::EXCEPTION_READ_ERROR, $absolutePath));
+ }
+ $this->content = \SplFixedArray::fromArray($content, false);
+ return $this;
+ }
+
+ /**
+ * Gets the source iterator.
+ *
+ * @param string|null $strContent The string content
+ *
+ * @throws \Exception if self::content is empty or splitting on linefeed has failed
+ * @return \Generator The source iterator.
+ */
+ private function getSourceGenerator(?string $strContent = null): \Generator
+ {
+ if (is_null($strContent)) {
+ if(is_null($this->content)) {
+ throw new \Exception(self::EXCEPTION_LINE_SPLIT);
+ }else {
+ $source = $this->content;
+ }
+ } else {
+ $simplerLineFeeds = preg_replace('/(\r\n|\r)$/', "\n", (string) $strContent);
+ $source = preg_split("/\n/m", $simplerLineFeeds, 0, \PREG_SPLIT_DELIM_CAPTURE);
+ if (!is_array($source) || !count($source)) {
+ throw new \Exception(self::EXCEPTION_LINE_SPLIT);
+ }
+ $source = \SplFixedArray::fromArray($source, false);
+ }
+ foreach ($source as $key => $value) {
+ yield ++$key => $value;
+ }
+ }
+
+ /**
+ * Parse Yaml lines into a hierarchy of Node
+ *
+ * @param ?string $strContent The Yaml string or null to parse loaded content
+ *
+ * @throws \Exception if content is not available as $strContent or as $this->content (from file)
+ * @throws \ParseError if any error during parsing or building
+ *
+ * @return array|YamlObject|null null on errors if NO_PARSING_EXCEPTIONS is set, otherwise an array of YamlObject or just YamlObject
+ */
+ public function parse(?string $strContent = null)
+ {
+ if(!is_null($strContent)) {
+ $this->content = null;
+ }
+ $generator = $this->getSourceGenerator($strContent);
+ $previous = $root = new Nodes\Root();
+ $debugNodeFactory = $this->_debug === 1;
+ try {
+ foreach ($generator as $lineNB => $lineString) {
+ $node = NodeFactory::get($lineString, $lineNB, $debugNodeFactory);
+ if ($this->needsSpecialProcess($node, $previous)) continue;
+ $this->_attachBlankLines($previous);
+ $target = match ($node->indent <=> $previous->indent) {
+ -1 => $previous->getTargetOnLessIndent($node),
+ 0 => $previous->getTargetOnEqualIndent($node),
+ 1 => $previous->getTargetOnMoreIndent($node)
+ };
+ $previous = $target->add($node);
+ }
+ $this->_attachBlankLines($previous);
+ return (new Builder($this->_options, $this->_debug))->buildContent($root);
+ } catch (\Throwable $e) {
+ $this->onError($e);
+ }
+ }
+
+
+ /**
+ * Attach blank (empty) Nodes saved in $_blankBuffer to their parent (it means they are meaningful content)
+ *
+ * @param NodeGeneric $previous The previous Node
+ *
+ * @return null
+ */
+ private function _attachBlankLines(NodeGeneric $previous)
+ {
+ foreach ($this->_blankBuffer as $blankNode) {
+ if ($blankNode !== $previous) {
+ $blankNode->getParent()->add($blankNode);
+ }
+ }
+ $this->_blankBuffer = [];
+ }
+
+ /**
+ * For certain (special) Nodes types some actions are required BEFORE parent assignment
+ *
+ * @param NodeGeneric $previous The previous Node
+ *
+ * @return boolean if True self::parse skips changing previous and adding to parent
+ * @see self::parse
+ */
+ private function needsSpecialProcess(NodeGeneric $current, NodeGeneric $previous): bool
+ {
+ $deepest = $previous->getDeepestNode();
+ if ($deepest instanceof Nodes\Partial) {
+ return $deepest->specialProcess($current, $this->_blankBuffer);
+ } elseif (!($current instanceof Nodes\Partial)) {
+ return $current->specialProcess($previous, $this->_blankBuffer);
+ }
+ return false;
+ }
+
+ private function onError(\Throwable $e)
+ {
+ $file = $this->filePath ? realpath($this->filePath) : '#YAML STRING#';
+ $message = $e->getMessage() . "\n " . $e->getFile() . ":" . $e->getLine();
+ if ($this->_options & self::NO_PARSING_EXCEPTIONS) {
+ self::$error = $message;
+ return null;
+ }
+ throw new \Exception($message . " for $file:" . $e->getLine(), 1, $e);
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/src/NodeFactory.php b/classes/vendor/81x/dallgoot/yaml/src/NodeFactory.php
new file mode 100644
index 0000000..9644a85
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/src/NodeFactory.php
@@ -0,0 +1,154 @@
+
+ * @license Apache 2.0
+ * @link https://github.com/dallgoot/yaml
+ */
+class NodeFactory
+{
+ private const JSON_OPTIONS = \JSON_PARTIAL_OUTPUT_ON_ERROR | \JSON_UNESCAPED_SLASHES;
+
+ final public static function get(string $nodeString, int $line = 0, bool $debug = false): NodeGeneric
+ {
+ $node = null;
+ $trimmed = ltrim($nodeString);
+ $match = (bool) preg_match(Regex::KEY, $trimmed, $matches);
+ $node = match(true) {
+ $trimmed === '' => new Blank($nodeString, $line),
+ str_starts_with($trimmed, '...') => new Nodes\DocEnd($nodeString, $line),
+ $match => new Nodes\Key($nodeString, $line, $matches),
+ default => self::onCharacter($trimmed[0], $nodeString, $line)
+ };
+ if ($debug) echo $line . ":" . get_class($node) . "\n";
+ return $node;
+ }
+
+
+ private static function onCharacter(string $first, string $nodeString, int $line): NodeGeneric
+ {
+ return match ($first) {
+ '-' => self::onHyphen($nodeString, $line),
+ '>' => new Nodes\LiteralFolded($nodeString, $line),
+ '|' => new Nodes\Literal($nodeString, $line),
+ '"', "'" => self::onQuoted($first, $nodeString, $line),
+ '#' => new Nodes\Comment(ltrim($nodeString), $line),
+ '%' => self::onDirective($nodeString, $line),
+ '{', '[' => self::onCompact($nodeString, $line),
+ ':' => new Nodes\SetValue($nodeString, $line),
+ '?' => new Nodes\SetKey($nodeString, $line),
+ '*', '&' => self::onNodeAction($nodeString, $line),
+ '!' => new Nodes\Tag($nodeString, $line),
+ default => new Nodes\Scalar($nodeString, $line),
+ };
+ }
+
+
+ /**
+ * Return the correct Node Object between NodeComment OR NodeDirective
+ *
+ * @param string $nodeString The node string
+ * @param integer $line The line
+ *
+ * @return NodeGeneric
+ */
+ private static function onDirective(string $nodeString, int $line): NodeGeneric
+ {
+ if (
+ (bool) preg_match(Regex::DIRECTIVE_TAG, $nodeString)
+ || (bool) preg_match(Regex::DIRECTIVE_VERSION, $nodeString)
+ ) {
+ return new Nodes\Directive(ltrim($nodeString), $line);
+ } else {
+ throw new \ParseError("Invalid/Unknown Directive", 1);
+ }
+ }
+
+ /**
+ * Set $node type and value when $nodevalue starts with a quote (simple or double)
+ *
+ * @param string $nodeString The node value
+ * @param int $line The line
+ *
+ * @return NodeGeneric
+ */
+ private static function onQuoted(string $first, string $nodeString, int $line): NodeGeneric
+ {
+ return Regex::isProperlyQuoted(trim($nodeString)) ? new Nodes\Quoted($nodeString, $line)
+ : new Nodes\Partial($nodeString, $line);
+ }
+
+ /**
+ * Determines the Node type and value when a compact object/array syntax is found
+ *
+ * @param string $nodeString The value assumed to start with { or [ or characters
+ * @param int $line The line
+ *
+ * @return NodeGeneric
+ */
+ private static function onCompact(string $nodeString, int $line): NodeGeneric
+ {
+ json_decode($nodeString, false, 512, self::JSON_OPTIONS);
+ if (json_last_error() === \JSON_ERROR_NONE) {
+ return new Nodes\JSON($nodeString, $line);
+ } else {
+ $backtrack_setting = "pcre.backtrack_limit";
+ ini_set($backtrack_setting, "-1");
+ $isMapping = (bool) preg_match(Regex::MAPPING, trim($nodeString));
+ $isSequence = (bool) preg_match(Regex::SEQUENCE, trim($nodeString));
+ ini_restore($backtrack_setting);
+
+ return match(true) {
+ $isMapping => new Nodes\CompactMapping($nodeString, $line),
+ $isSequence => new Nodes\CompactSequence($nodeString, $line),
+ default => new Nodes\Partial($nodeString, $line),
+ };
+ }
+ }
+
+ /**
+ * Determines Node type and value when an hyphen "-" is found
+ *
+ * @param string $nodeString The node string value
+ * @param int $line The line
+ *
+ * @return NodeGeneric
+ */
+ private static function onHyphen(string $nodeString, int $line): NodeGeneric
+ {
+ return match(true) {
+ str_starts_with($nodeString, '---') => new Nodes\DocStart($nodeString, $line),
+ (bool) preg_match(Regex::ITEM, ltrim($nodeString)) => new Nodes\Item($nodeString, $line),
+ default => new Nodes\Scalar($nodeString, $line),
+ };
+ }
+
+ /**
+ * Sets Node type and value according to $nodeString when one of these characters is found : !,&,*
+ *
+ * @param string $nodeString The node value
+ * @param int $line The line
+ *
+ *@todo replace $action[0] with $first if applicable
+ */
+ private static function onNodeAction(string $nodeString, int $line): NodeGeneric
+ {
+ if (!((bool) preg_match(Regex::NODE_ACTIONS, trim($nodeString), $matches))) {
+ return new Nodes\Scalar($nodeString, $line);
+ }
+ return new Nodes\Anchor($nodeString, $line);
+ }
+
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/src/NodeList.php b/classes/vendor/81x/dallgoot/yaml/src/NodeList.php
new file mode 100644
index 0000000..64f14d1
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/src/NodeList.php
@@ -0,0 +1,191 @@
+
+ * @license Apache 2.0
+ * @link https://github.com/dallgoot/yaml
+ */
+class NodeList extends \SplDoublyLinkedList
+{
+ const MAPPING = 1;
+ const MULTILINE = 2;
+ const SEQUENCE = 4;
+ const SET = 8;
+
+ public $type;
+
+ /**
+ * NodeList constructor
+ *
+ * @param NodeGeneric|null $node (optional) a node that will be pushed as first element
+ */
+ public function __construct(NodeGeneric $node = null)
+ {
+ // parent::__construct();
+ // $this->setIteratorMode(self::IT_MODE_KEEP);
+ if (!is_null($node)) {
+ $this->push($node);
+ }
+ }
+
+ public function has(string $nodeType): bool
+ {
+ $tmp = clone $this;
+ $tmp->rewind();
+ $fqn = __NAMESPACE__ . "\\Nodes\\$nodeType";
+ foreach ($tmp as $child) {
+ if ($child instanceof $fqn) return true;
+ }
+ return false;
+ }
+
+ public function hasContent(): bool
+ {
+ $tmp = clone $this;
+ $tmp->rewind();
+ foreach ($tmp as $child) {
+ if (
+ !($child instanceof Comment)
+ && !($child instanceof Directive)
+ && !($child instanceof Blank)
+ && !($child instanceof Docstart
+ && is_null($child->value))
+ ) return true;
+ }
+ return false;
+ }
+
+ public function push($node): void
+ {
+ $type = null;
+ if ($node instanceof Item) {
+ $type = self::SEQUENCE;
+ } elseif ($node instanceof Key) {
+ $type = self::MAPPING;
+ } elseif ($node->isOneOf('SetKey', 'SetValue')) {
+ $type = self::SET;
+ } elseif ($node instanceof Scalar) {
+ $type = self::MULTILINE;
+ }
+ if (!is_null($type) && $this->checkTypeCoherence($type)) {
+ $this->type = $type;
+ }
+ parent::push($node);
+ }
+
+ /**
+ * Verify that the estimated type is coherent with this list current $type
+ *
+ * @param int $estimatedType The estimated type
+ *
+ * @return boolean True if coherent, False otherwise
+ * @todo implement invalid cases
+ */
+ public function checkTypeCoherence($estimatedType): bool
+ {
+ // if ($this->type === self::MAPPING) {
+ // if ($estimatedType === self::SEQUENCE) {
+ // throw new \ParseError("Error : no coherence in types", 1);
+ // }
+ // }
+ return (bool) $estimatedType;
+ }
+
+ public function build(&$parent = null)
+ {
+ switch ($this->type) {
+ case self::MAPPING: //fall through
+ case self::SET:
+ $collect = $parent ?? new \stdClass;
+ return $this->buildList($collect);
+ case self::SEQUENCE:
+ $collect = $parent ?? [];
+ return $this->buildList($collect);
+ default:
+ $this->filterComment();
+ // return Nodes\Scalar::getScalar($this->buildMultiline());
+ return (new Nodes\Scalar('', 0))->getScalar($this->buildMultiline(), true);
+ }
+ }
+
+ public function buildList(&$collector)
+ {
+ $this->rewind();
+ foreach ($this as $child) {
+ $child->build($collector);
+ }
+ return $collector;
+ }
+
+ public function buildMultiline(): string
+ {
+ $output = '';
+ $list = clone $this;
+ if ($list->count() > 0) {
+ $list->rewind();
+ $first = $list->shift();
+ $output = trim($first->raw);
+ foreach ($list as $child) {
+ if ($child instanceof Scalar) {
+ $separator = isset($output[-1]) && $output[-1] === "\n" ? '' : ' ';
+ $output .= $separator . trim($child->raw);
+ } elseif ($child instanceof Blank) {
+ $output .= "\n";
+ } else {
+ $child->build();
+ }
+ }
+ }
+ return trim($output);
+ }
+
+ /**
+ * Remove NodeComment and returns a new one
+ *
+ * @return NodeList a new NodeList without NodeComment in it
+ * @todo double check that NodeComment are built
+ */
+ public function filterComment(): NodeList
+ {
+ $this->rewind();
+ $out = new NodeList;
+ foreach ($this as $index => $child) {
+ if ($child instanceof Comment) {
+ // $child->build();
+ } else {
+ if ($child->value instanceof Comment) {
+ // $child->value->build();
+ // $child->value = null;
+ } elseif ($child->value instanceof NodeList) {
+ $child->value = $child->value->filterComment();
+ }
+ $out->push($child);
+ }
+ }
+ $out->rewind();
+ return $out;
+ }
+
+ /**
+ * Provides a slimmer output when using var_dump Note: currently PHP ignores it on SPL types
+ * @todo activate when PHP supports it
+ */
+ // public function __debugInfo()
+ // {
+ // return ['type'=> Y::getName($this->type), 'dllist'=> $this->dllist];
+ // }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/src/Nodes/Anchor.php b/classes/vendor/81x/dallgoot/yaml/src/Nodes/Anchor.php
new file mode 100644
index 0000000..171e179
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/src/Nodes/Anchor.php
@@ -0,0 +1,36 @@
+
+ * @license Apache 2.0
+ * @link https://github.com/dallgoot/yaml
+ */
+class Anchor extends Actions
+{
+ public function &build(&$parent = null)
+ {
+ $name = substr((string) $this->anchor, 1);
+ $yamlObject = $this->getRoot()->getYamlObject();
+ if (str_starts_with((string) $this->anchor, "*")) {
+ try {
+ return $yamlObject->getReference($name);
+ } catch (\Throwable $e) {
+ throw new \ParseError("Unknown anchor : '$name' this:" . $this->anchor, 1, $e);
+ }
+ } else {
+ $built = is_null($this->value) ? null : $this->value->build($parent);
+ return $yamlObject->addReference($name, $built);
+ }
+ }
+
+ public function isAwaitingChild(NodeGeneric $node): bool
+ {
+ return is_null($this->value);
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/src/Nodes/Blank.php b/classes/vendor/81x/dallgoot/yaml/src/Nodes/Blank.php
new file mode 100644
index 0000000..941751a
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/src/Nodes/Blank.php
@@ -0,0 +1,50 @@
+
+ * @license Apache 2.0
+ * @link https://github.com/dallgoot/yaml
+ */
+class Blank extends NodeGeneric
+{
+ public function add(NodeGeneric $child): NodeGeneric
+ {
+ if ($this->_parent instanceof NodeGeneric) {
+ return $this->_parent->add($child);
+ } else {
+ throw new \ParseError(__METHOD__ . " no parent to add to", 1);
+ }
+ }
+
+ public function specialProcess(NodeGeneric &$previous, array &$emptyLines): bool
+ {
+ $deepest = $previous->getDeepestNode();
+ if ($previous instanceof Scalar) {
+ $emptyLines[] = $this->setParent($previous->getParent());
+ } elseif ($deepest instanceof Literals) {
+ $emptyLines[] = $this->setParent($deepest);
+ }
+ return true;
+ }
+
+ public function build(&$parent = null)
+ {
+ return "\n";
+ }
+
+ public function getTargetOnEqualIndent(NodeGeneric &$node): ?NodeGeneric
+ {
+ return $this->getParent($node->indent);
+ }
+
+ public function getTargetOnMoreIndent(NodeGeneric &$node): ?NodeGeneric
+ {
+ return $this->getParent($node->indent);
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/src/Nodes/Comment.php b/classes/vendor/81x/dallgoot/yaml/src/Nodes/Comment.php
new file mode 100644
index 0000000..c21c303
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/src/Nodes/Comment.php
@@ -0,0 +1,28 @@
+
+ * @license Apache 2.0
+ * @link https://github.com/dallgoot/yaml
+ */
+class Comment extends NodeGeneric
+{
+ public function specialProcess(NodeGeneric &$previous, array &$emptyLines): bool
+ {
+ $previous->getRoot()->add($this);
+ return true;
+ }
+
+ public function build(&$parent = null)
+ {
+ $root = $this->getRoot();
+ $yamlObject = $root->getYamlObject();
+ $yamlObject->addComment($this->line, $this->raw);
+ return null;
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/src/Nodes/CompactMapping.php b/classes/vendor/81x/dallgoot/yaml/src/Nodes/CompactMapping.php
new file mode 100644
index 0000000..8c74e1f
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/src/Nodes/CompactMapping.php
@@ -0,0 +1,43 @@
+
+ * @license Apache 2.0
+ * @link https://github.com/dallgoot/yaml
+ */
+class CompactMapping extends NodeGeneric
+{
+ public function __construct(string $nodeString, ?int $line)
+ {
+ parent::__construct($nodeString, $line);
+ preg_match_all(Regex::MAPPING_VALUES, trim(substr(trim($nodeString), 1, -1)), $matches);
+ foreach ($matches['k'] as $index => $property) {
+ $pair = $property . ': ' . trim($matches['v'][$index]);
+ $child = NodeFactory::get($pair, (int) $line);
+ $child->indent = null;
+ $this->add($child);
+ }
+ }
+
+ public function build(&$parent = null): ?Compact
+ {
+ if (is_null($this->value)) {
+ return null;
+ }
+ if ($this->value instanceof NodeGeneric) {
+ $this->value = new NodeList($this->value);
+ $this->value->type = NodeList::MAPPING;
+ }
+ $obj = (object) $this->value->build();
+ return new Compact($obj);
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/src/Nodes/CompactSequence.php b/classes/vendor/81x/dallgoot/yaml/src/Nodes/CompactSequence.php
new file mode 100644
index 0000000..76b127e
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/src/Nodes/CompactSequence.php
@@ -0,0 +1,45 @@
+
+ * @license Apache 2.0
+ * @link https://github.com/dallgoot/yaml
+ */
+class CompactSequence extends NodeGeneric
+{
+ public function __construct(string $nodeString, ?int $line)
+ {
+ parent::__construct($nodeString, $line);
+ preg_match_all(Regex::SEQUENCE_VALUES, trim(substr(trim($nodeString), 1, -1)), $matches);
+ foreach ($matches['item'] as $key => $item) {
+ $i = new Item('', (int) $line);
+ $i->indent = null;
+ $itemValue = NodeFactory::get(trim($item));
+ $itemValue->indent = null;
+ $i->add($itemValue);
+ $this->add($i);
+ }
+ }
+
+ public function build(&$parent = null): ?Compact
+ {
+ if (is_null($this->value)) {
+ return null;
+ }
+ if ($this->value instanceof NodeGeneric) {
+ $this->value = new NodeList($this->value);
+ $this->value->type = NodeList::SEQUENCE;
+ }
+ $arr = (array) $this->value->build();
+ return new Compact($arr);
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/src/Nodes/Directive.php b/classes/vendor/81x/dallgoot/yaml/src/Nodes/Directive.php
new file mode 100644
index 0000000..01915cb
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/src/Nodes/Directive.php
@@ -0,0 +1,58 @@
+
+ * @license Apache 2.0
+ * @link https://github.com/dallgoot/yaml
+ */
+class Directive extends NodeGeneric
+{
+ private const ERROR_BUILDING = "Error : can not build Directive";
+ private const WARNING_LOWER_VERSION = "The declared version '%s' is obsolete, there may be features that are deprecated and therefore not handled, minimum supported is: " . Yaml::VERSION_SUPPORT;
+ private const WARNING_HIGHER_VERSION = "The declared version '%s' is not yet supported, minimum supported is: " . Yaml::VERSION_SUPPORT;
+
+ /**
+ * Builds a Directive : update YamlObject if applicable.
+ *
+ * @param object|array $parent The parent
+ *
+ * @throws \ParseError If Tag handle has been already set before.
+ */
+ public function build(&$parent = null): void
+ {
+ if (preg_match(Regex::DIRECTIVE_TAG, $this->raw, $matches)) {
+ try {
+ $yamlObject = $this->getRoot()->getYamlObject();
+ //Try registering the handle in TagFactory
+ TagFactory::registerHandle($matches['handle'], $matches['uri']);
+ $yamlObject->addTag($matches['handle'], $matches['uri']);
+ } catch (\Throwable $e) {
+ throw new \ParseError(self::ERROR_BUILDING, 1, $e);
+ }
+ }
+ // TODO : is that pertinent ? : it crashes tests only for a notice
+ // if (preg_match(Regex::DIRECTIVE_VERSION, $this->raw, $matches)) {
+ // $contentVersion = (float) $matches['version'];
+ // if ($contentVersion > Yaml::VERSION_SUPPORT) {
+ // trigger_error(sprintf(self::WARNING_HIGHER_VERSION,$matches['version']), \E_USER_NOTICE );
+ // }
+ // if ($contentVersion < Yaml::VERSION_SUPPORT) {
+ // trigger_error(sprintf(self::WARNING_LOWER_VERSION, $matches['version']), \E_USER_NOTICE );
+ // }
+ // }
+ return;
+ }
+
+ public function add(NodeGeneric $child): NodeGeneric
+ {
+ return $child;
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/src/Nodes/DocEnd.php b/classes/vendor/81x/dallgoot/yaml/src/Nodes/DocEnd.php
new file mode 100644
index 0000000..2a28662
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/src/Nodes/DocEnd.php
@@ -0,0 +1,20 @@
+
+ * @license Apache 2.0
+ * @link https://github.com/dallgoot/yaml
+ */
+class DocEnd extends NodeGeneric
+{
+ public function build(&$parent = null)
+ {
+ // Does nothing
+ return null;
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/src/Nodes/DocStart.php b/classes/vendor/81x/dallgoot/yaml/src/Nodes/DocStart.php
new file mode 100644
index 0000000..a2a2e42
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/src/Nodes/DocStart.php
@@ -0,0 +1,76 @@
+
+ * @license Apache 2.0
+ * @link https://github.com/dallgoot/yaml
+ */
+class DocStart extends NodeGeneric
+{
+ public function __construct(string $nodeString, ?int $line)
+ {
+ parent::__construct($nodeString, $line);
+ $rest = substr(ltrim($nodeString), 3);
+ if (!empty($rest)) {
+ $n = NodeFactory::get($rest, (int) $line);
+ $n->indent = null;
+ $this->add($n);
+ }
+ }
+
+ public function add(NodeGeneric $child): NodeGeneric
+ {
+ if ($this->value instanceof NodeGeneric) {
+ return $this->value->add($child);
+ } else {
+ return parent::add($child);
+ }
+ }
+
+ public function build(&$parent = null)
+ {
+ if (is_null($parent)) {
+ throw new \Exception(__METHOD__ . " expects a YamlObject as parent", 1);
+ }
+ if (!is_null($this->value)) {
+ if ($this->value instanceof Tag) {
+ preg_match(Regex::TAG_PARTS, $this->value->raw, $tagparts);
+ if (preg_match("/(?(DEFINE)" . Regex::TAG_URI . ')(?&url)/', $tagparts['tagname'], $matches)) {
+ $parent->addTag($tagparts['handle'], $tagparts['tagname']);
+ } else {
+ $this->value->build($parent);
+ }
+ } else {
+ $text = $this->value->build($parent);
+ !is_null($text) && $parent->setText($text);
+ }
+ }
+ return null;
+ }
+
+ public function isAwaitingChild(NodeGeneric $node): bool
+ {
+ return $this->value instanceof NodeGeneric && $this->value->isOneOf('Anchor', 'Literal', 'LiteralFolded');
+ }
+
+ public function getTargetOnEqualIndent(NodeGeneric &$node): NodeGeneric
+ {
+ if ($this->value instanceof NodeGeneric) {
+ if ($this->value instanceof Tag) {
+ if (!preg_match("/" . Regex::TAG_URI . "/", $this->value->raw)) {
+ return $this->value;
+ }
+ } elseif ($this->value->isAwaitingChild($node)) {
+ return $this->value;
+ }
+ }
+ return $this->getParent();
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/src/Nodes/Generic/Actions.php b/classes/vendor/81x/dallgoot/yaml/src/Nodes/Generic/Actions.php
new file mode 100644
index 0000000..62a3322
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/src/Nodes/Generic/Actions.php
@@ -0,0 +1,43 @@
+
+ * @license Apache 2.0
+ * @link https://github.com/dallgoot/yaml
+ */
+abstract class Actions extends NodeGeneric
+{
+ // public $anchor = '';
+ // public $tag = '';
+
+ public function __construct(string $nodeString, int $line)
+ {
+ parent::__construct($nodeString, $line);
+ $trimmed = ltrim($nodeString);
+ $pos = strpos($trimmed, ' ');
+ $name = $trimmed;
+ if (is_int($pos)) {
+ $name = strstr($trimmed, ' ', true);
+ $value = trim(substr($trimmed, $pos + 1));
+ if ($value !== '') {
+ $child = NodeFactory::get($value, $line);
+ $child->indent = null;
+ parent::add($child);
+ }
+ }
+ if ($this instanceof Tag) {
+ $this->tag = $name;
+ } else {
+ $this->anchor = $name;
+ }
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/src/Nodes/Generic/Literals.php b/classes/vendor/81x/dallgoot/yaml/src/Nodes/Generic/Literals.php
new file mode 100644
index 0000000..2b9abcf
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/src/Nodes/Generic/Literals.php
@@ -0,0 +1,122 @@
+
+ * @license Apache 2.0
+ * @link https://github.com/dallgoot/yaml
+ * @todo implement Indentation indicator 8.1.1
+ */
+abstract class Literals extends NodeGeneric
+{
+ /** @var NodeList */
+ public $value;
+ public $identifier;
+
+ abstract protected function getFinalString(NodeList $list, int $refIndent = null): string;
+
+ public function __construct(string $nodeString, int $line)
+ {
+ parent::__construct($nodeString, $line);
+ if (isset($nodeString[1]) && in_array($nodeString[1], ['-', '+'])) {
+ $this->identifier = $nodeString[1];
+ }
+ }
+
+ public function add(NodeGeneric $child): NodeGeneric
+ {
+ if (is_null($this->value)) $this->value = new NodeList();
+ $candidate = $child;
+ if (!$child->isOneOf('Scalar', 'Blank', 'Comment', 'Quoted')) {
+ $candidate = new Scalar((string) $child->raw, $child->line);
+ }
+ return parent::add($candidate);
+ }
+
+ protected static function litteralStripLeading(NodeList &$list): void
+ {
+ $list->rewind();
+ while (!$list->isEmpty() && $list->bottom() instanceof Blank) { //remove leading blank
+ $list->shift();
+ }
+ $list->rewind();
+ }
+
+ protected static function litteralStripTrailing(NodeList &$list): void
+ {
+ $list->rewind();
+ while (!$list->isEmpty() && $list->top() instanceof Blank) { //remove trailing blank
+ $list->pop();
+ }
+ $list->rewind();
+ }
+
+ /**
+ * Builds a litteral (folded or not)
+ * As per Documentation : 8.1.1.2. Block Chomping Indicator
+ * Chomping controls how final line breaks and trailing empty lines are interpreted.
+ * YAML provides three chomping methods:
+ * Clip (default behavior) : FINAL_LINE_BREAK, NO TRAILING EMPTY LINES
+ * Strip (“-” chomping indicator) NO FINAL_LINE_BREAK, NO TRAILING EMPTY LINES
+ * Keep (“+” chomping indicator) FINAL_LINE_BREAK && TRAILING EMPTY LINES
+ */
+ public function build(&$parent = null)
+ {
+ $result = '';
+ if (!is_null($this->tag)) {
+ $output = TagFactory::transform($this->tag, $this->value);
+ return $output instanceof Tagged ? $output : $output->build($parent);
+ }
+ if (!is_null($this->value)) {
+ $tmp = $this->getFinalString($this->value->filterComment());
+ $result = $this->identifier === '-' ? $tmp : $tmp . "\n";
+ }
+ if ($this->_parent instanceof Root) {
+ $this->_parent->getYamlObject()->setText($result);
+ return null;
+ } else {
+ return $result;
+ }
+ }
+
+ /**
+ * Gets the correct string for child value.
+ *
+ * @todo double check behaviour for KEY and ITEM
+ */
+ protected function getChildValue(NodeGeneric $child, ?int $refIndent = 0): string
+ {
+ $value = $child->value;
+ $start = '';
+ if (is_null($value)) {
+ if ($child instanceof Quoted) {
+ return $child->build();
+ } elseif ($child instanceof Blank) {
+ return '';
+ } else {
+ return ltrim($child->raw);
+ }
+ } elseif ($value instanceof Scalar) {
+ $value = new NodeList($value);
+ } elseif ($value instanceof NodeList && !($child instanceof Scalar)) {
+ $start = ltrim($child->raw) . "\n";
+ }
+ return $start . $this->getFinalString($value, $refIndent);
+ }
+
+ public function isAwaitingChild(NodeGeneric $node): bool
+ {
+ return true;
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/src/Nodes/Generic/NodeGeneric.php b/classes/vendor/81x/dallgoot/yaml/src/Nodes/Generic/NodeGeneric.php
new file mode 100644
index 0000000..ed02334
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/src/Nodes/Generic/NodeGeneric.php
@@ -0,0 +1,243 @@
+
+ * @license Apache 2.0
+ * @link https://github.com/dallgoot/yaml
+ */
+abstract class NodeGeneric
+{
+ /** @var null|string|boolean */
+ public $identifier;
+
+ protected ?self $_parent = null;
+
+
+ public ?int $indent = -1;
+
+ public int $line = 0;
+
+ public string $raw = '';
+ /** @var null|self|NodeList */
+ public $value;
+
+ public ?string $anchor = null;
+
+ public ?string $tag = null;
+
+
+ /**
+ *
+ * @param array|object|null $parent The parent collector or NULL otherwise
+ *
+ * @return mixed whatever the build process returns
+ */
+ public abstract function build(&$parent = null);
+
+ /**
+ * Create the Node object and parses $nodeString
+ *
+ * @todo make it more permissive to tabs but replacement
+ */
+ public function __construct(string $nodeString, ?int $line = 0)
+ {
+ $this->raw = $nodeString;
+ $this->line = (int) $line;
+ $nodeValue = preg_replace("/^\t+/m", " ", $nodeString);
+ $this->indent = strspn($nodeValue, ' ');
+ }
+
+ /**
+ * Sets the parent of the current Node
+ *
+ */
+ protected function setParent(NodeGeneric $node): NodeGeneric
+ {
+ $this->_parent = $node;
+ return $this;
+ }
+
+ /**
+ * Gets the ancestor with specified $indent or the direct $_parent
+ *
+ */
+ public function getParent(?int $indent = null): ?NodeGeneric
+ {
+ if (!is_int($indent)) {
+ if ($this->_parent instanceof NodeGeneric) {
+ return $this->_parent;
+ } else {
+ throw new \Exception("Cannnot find a parent for " . get_class($this), 1);
+ }
+ }
+ $cursor = $this->getParent();
+ while (
+ !($cursor instanceof Root)
+ && (is_null($cursor->indent)
+ || $cursor->indent >= $indent)
+ ) {
+ if ($cursor->_parent) {
+ $cursor = $cursor->_parent;
+ } else {
+ break;
+ }
+ }
+ return $cursor;
+ }
+
+ /**
+ * Gets the root of the structure map (or current Yaml document)
+ *
+ * @throws \Exception (description)
+ */
+ protected function getRoot(): Root
+ {
+ if (is_null($this->_parent)) {
+ throw new \Exception(__METHOD__ . ": can only be used when Node has a parent set", 1);
+ }
+ $pointer = $this;
+ do {
+ if ($pointer->_parent instanceof NodeGeneric) {
+ $pointer = $pointer->_parent;
+ } else {
+ throw new \Exception("Node has no _parent set : " . get_class($pointer), 1);
+ }
+ } while (!($pointer instanceof Root));
+ return $pointer;
+ }
+
+ /**
+ * Set the value for the current Node :
+ * - if value is null , then value = $child (Node)
+ * - if value is Node, then value is a NodeList with (previous value AND $child)
+ * - if value is a NodeList, push $child into
+ *
+ */
+ public function add(NodeGeneric $child): NodeGeneric
+ {
+ $child->setParent($this);
+ if (is_null($this->value)) {
+ $this->value = $child;
+ } else {
+ if ($this->value instanceof NodeGeneric) {
+ $this->value = new NodeList($this->value);
+ }
+ $this->value->push($child);
+ }
+ return $child;
+ }
+
+ /**
+ * Gets the deepest node.
+ */
+ public function getDeepestNode(): NodeGeneric
+ {
+ $cursor = $this;
+ while ($cursor->value instanceof NodeGeneric) {
+ $cursor = $cursor->value;
+ }
+ return $cursor;
+ }
+
+ public function specialProcess(
+ /** @scrutinizer ignore-unused */
+ NodeGeneric &$previous,
+ /** @scrutinizer ignore-unused */
+ array &$emptyLines
+ ): bool {
+ return false;
+ }
+
+ /**
+ * Find parent target when current Node indentation is lesser than previous node indentation
+ *
+ */
+ public function getTargetOnLessIndent(NodeGeneric &$node): ?NodeGeneric
+ {
+ $supposedParent = $this->getParent($node->indent);
+ if ($node instanceof Item && $supposedParent instanceof Root) {
+ if ($supposedParent->value->has('Key')) {
+ $supposedParent->value->setIteratorMode(\SplDoublyLinkedList::IT_MODE_LIFO);
+ foreach ($supposedParent->value as $child) {
+ if ($child instanceof Key) {
+ $supposedParent->value->setIteratorMode(\SplDoublyLinkedList::IT_MODE_FIFO);
+ // $supposedParent->value->rewind();
+ return $child;
+ }
+ }
+ }
+ }
+ return $supposedParent;
+ }
+
+ /**
+ * Find parent target when current Node indentation is equal to previous node indentation
+ *
+ */
+ public function getTargetOnEqualIndent(NodeGeneric &$node): ?NodeGeneric
+ {
+ return $this->getParent();
+ }
+
+ /**
+ * Find parent target when current Node indentation is superior than previous node indentation
+ *
+ */
+ public function getTargetOnMoreIndent(NodeGeneric &$node): ?NodeGeneric
+ {
+ return $this->isAwaitingChild($node) ? $this : $this->getParent();
+ }
+
+ protected function isAwaitingChild(NodeGeneric $node): bool
+ {
+ return false;
+ }
+
+ /**
+ * Determines if $subject is one of the Node types provided (as strings) in $comparison array
+ * A node type is one of the class found in "nodes" folder.
+ *
+ * @param string ...$classNameList A list of string where each is a Node type e.g. 'Key', 'Blank', etc.
+ *
+ * @return boolean True if $subject is one of $comparison, False otherwise.
+ */
+ public function isOneOf(...$classNameList): bool
+ {
+ foreach ($classNameList as $className) {
+ $fqn = "Dallgoot\\Yaml\\Nodes\\$className";
+ if ($this instanceof $fqn) return true;
+ }
+ return false;
+ }
+
+ /**
+ * PHP internal function for debugging purpose : simplify output provided by 'var_dump'
+ *
+ * @return array the Node properties and respective values displayed by 'var_dump'
+ */
+ public function __debugInfo(): array
+ {
+ $props = [];
+ $props['line->indent'] = "$this->line -> $this->indent";
+ if ($this->identifier) $props['identifier'] = "($this->identifier)";
+ if ($this->anchor) $props['anchor'] = "($this->anchor)";
+ if ($this->tag) $props['tag'] = "($this->tag)";
+ if ($this->value) $props['value'] = $this->value;
+ // $props['value'] = $this->value;
+ $props['raw'] = $this->raw;
+ if (!$this->_parent) $props['parent'] = 'NO PARENT!!!';
+ return $props;
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/src/Nodes/Item.php b/classes/vendor/81x/dallgoot/yaml/src/Nodes/Item.php
new file mode 100644
index 0000000..59c1384
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/src/Nodes/Item.php
@@ -0,0 +1,96 @@
+
+ * @license Apache 2.0
+ * @link https://github.com/dallgoot/yaml
+ */
+class Item extends NodeGeneric
+{
+ public function __construct(string $nodeString, int $line)
+ {
+ parent::__construct($nodeString, $line);
+ preg_match(Regex::ITEM, ltrim($nodeString), $matches);
+ $value = isset($matches[1]) ? ltrim($matches[1]) : null;
+ if (!empty($value)) {
+ $n = NodeFactory::get($value, $line);
+ $n->indent = $this->indent + 2;
+ $this->add($n);
+ }
+ }
+
+ public function add(NodeGeneric $child): NodeGeneric
+ {
+ $value = $this->value;
+ if ($value instanceof Key && $child instanceof Key) {
+ if ($value->indent === $child->indent) {
+ return parent::add($child);
+ } elseif ($value->isAwaitingChild($child)) {
+ return $value->add($child);
+ } else {
+ // throw new \ParseError('key ('.$value->identifier.')@'.$value->line.' has already a value', 1);
+ throw new \ParseError('key @' . $value->line . ' has already a value', 1);
+ }
+ }
+ return parent::add($child);
+ }
+
+ public function getTargetOnEqualIndent(NodeGeneric &$node): NodeGeneric
+ {
+ $supposedParent = $this->getParent();
+ if ($node->indent === $supposedParent->indent) {
+ return $supposedParent->getParent();
+ }
+ return $supposedParent;
+ }
+
+ public function getTargetOnMoreIndent(NodeGeneric &$node): NodeGeneric
+ {
+ return $this->value instanceof NodeGeneric && $this->value->isAwaitingChild($node) ? $this->value : $this;
+ }
+
+ /**
+ * Builds an item. Adds the item value to the parent array|Iterator
+ *
+ * @param array|YamlObject|null $parent The parent
+ *
+ * @throws \Exception if parent is another type than array or object Iterator
+ */
+ public function build(&$parent = null): ?array
+ {
+ if (!is_null($parent) && !is_array($parent) && !($parent instanceof YamlObject)) {
+ throw new \Exception("parent must be an array or YamlObject not " .
+ (is_object($parent) ? get_class($parent) : gettype($parent)));
+ }
+ $value = $this->value ? $this->value->build() : null;
+ if (is_null($parent)) {
+ return [$value];
+ } else {
+ // $ref = is_array($parent) ? $parent : iterator_to_array($parent);
+ // $numKeys = array_keys($ref);
+ // $key = count($numKeys) > 0 ? max($numKeys) + 1 : 0;
+ // $parent[$key] = $value;
+ $parent[] = $value;
+ return null;
+ }
+ }
+
+ public function isAwaitingChild(NodeGeneric $node): bool
+ {
+ if (is_null($this->value)) {
+ return true;
+ } elseif ($this->value instanceof SetKey && $node instanceof SetValue) {
+ return true;
+ } else {
+ return $this->getDeepestNode()->isAwaitingChild($node);
+ }
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/src/Nodes/JSON.php b/classes/vendor/81x/dallgoot/yaml/src/Nodes/JSON.php
new file mode 100644
index 0000000..dd94bd9
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/src/Nodes/JSON.php
@@ -0,0 +1,21 @@
+
+ * @license Apache 2.0
+ * @link https://github.com/dallgoot/yaml
+ */
+class JSON extends NodeGeneric
+{
+ private const JSON_OPTIONS = \JSON_PARTIAL_OUTPUT_ON_ERROR | \JSON_UNESCAPED_SLASHES;
+
+ public function build(&$parent = null)
+ {
+ return json_decode($this->raw, false, 512, self::JSON_OPTIONS);
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/src/Nodes/Key.php b/classes/vendor/81x/dallgoot/yaml/src/Nodes/Key.php
new file mode 100644
index 0000000..c3706b5
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/src/Nodes/Key.php
@@ -0,0 +1,136 @@
+
+ * @license Apache 2.0
+ * @link https://github.com/dallgoot/yaml
+ */
+class Key extends NodeGeneric
+{
+ const ERROR_NO_KEYNAME = self::class . ": key has NO IDENTIFIER on line %d";
+
+ public function __construct(string $nodeString, int $line, array $matches = null)
+ {
+ parent::__construct($nodeString, $line);
+ if (is_null($matches)) {
+ if (!((bool) preg_match(Regex::KEY, ltrim($nodeString), $matches))) {
+ throw new \ParseError("Not a KEY:VALUE syntax ($nodeString)", 1);
+ }
+ }
+ $this->setIdentifier($matches[1]);
+
+ $value = isset($matches[2]) ? trim($matches[2]) : null;
+ if (!empty($value)) {
+ $child = NodeFactory::get($value, $line);
+ $child->indent = null;
+ $this->add($child);
+ }
+ }
+
+ public function setIdentifier(string $keyString): void
+ {
+ if ($keyString === '') {
+ throw new \ParseError(sprintf(self::ERROR_NO_KEYNAME, $this->line));
+ } else {
+ $node = NodeFactory::get($keyString);
+ if ($node->isOneOf('Tag', 'Quoted')) {
+ $built = $node->build();
+ if (is_object($built)) {
+ $this->identifier = $built->value;
+ } else {
+ $this->identifier = (string) $node->build();
+ }
+ } elseif ($node instanceof Scalar) {
+ $this->identifier = trim($node->raw);
+ }
+ }
+ }
+
+ public function add(NodeGeneric $child): NodeGeneric
+ {
+ if ($this->value instanceof NodeGeneric && $this->value->isOneOf('Literal', 'LiteralFolded', 'Anchor')) {
+ return $this->value->add($child);
+ } else {
+ return parent::add($child);
+ }
+ }
+
+ public function getTargetOnEqualIndent(NodeGeneric &$node): NodeGeneric
+ {
+ if ($node instanceof Item) {
+ return $this;
+ }
+ return $this->getParent();
+ }
+
+ public function getTargetOnMoreIndent(NodeGeneric &$node): NodeGeneric
+ {
+ if (!is_null($this->value)) {
+ if ($this->getDeepestNode()->isAwaitingChild($node)) {
+ return $this->getDeepestNode();
+ }
+ }
+ return $this;
+ }
+
+
+ public function isAwaitingChild(NodeGeneric $node): bool
+ {
+ if (is_null($this->value) || $node instanceof Comment) {
+ return true;
+ } elseif ($this->value instanceof NodeGeneric) {
+ $current = $this->value;
+ } else {
+ $current = $this->value->current();
+ }
+ if ($current instanceof Comment) {
+ return true;
+ }
+ if ($current instanceof Scalar) {
+ return $node->isOneOf('Scalar', 'Blank');
+ }
+ if ($current->isOneOf('Key', 'Item')) {
+ $cClass = get_class($current);
+ return $node instanceof $cClass;
+ }
+ if ($current instanceof Literals) {
+ return $node->indent > $this->indent;
+ }
+ if ($current instanceof Anchor) {
+ return $current->isAwaitingChild($node);
+ }
+ return false;
+ }
+
+ /**
+ * Builds a key and set the property + value to the given parent
+ *
+ * @param object|array $parent The parent
+ *
+ * @throws \ParseError if Key has no name(identifier) Note: empty string is allowed
+ */
+ public function build(&$parent = null): ?object
+ {
+ if ($this->value instanceof Anchor) {
+ $result = &$this->value->build();
+ } else {
+ $result = is_null($this->value) ? null : $this->value->build();
+ }
+ if (is_null($parent)) {
+ $parent = new \stdClass;
+ $parent->{$this->identifier} = &$result;
+ return $parent;
+ } else {
+ $parent->{$this->identifier} = &$result;
+ return null;
+ }
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/src/Nodes/Literal.php b/classes/vendor/81x/dallgoot/yaml/src/Nodes/Literal.php
new file mode 100644
index 0000000..1d625dd
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/src/Nodes/Literal.php
@@ -0,0 +1,39 @@
+
+ * @license Apache 2.0
+ * @link https://github.com/dallgoot/yaml
+ */
+class Literal extends Literals
+{
+ public function getFinalString(NodeList $list, ?int $refIndent = null): string
+ {
+ $result = '';
+ $list = $list->filterComment();
+ if ($this->identifier !== '+') {
+ self::litteralStripTrailing($list);
+ }
+ if ($list->count()) {
+ $list->setIteratorMode(NodeList::IT_MODE_DELETE);
+ $first = $list->shift();
+ $indent = $refIndent ?? $first->indent;
+ $result = $this->getChildValue($first, $indent);
+ foreach ($list as $child) {
+ $value = "\n";
+ if (!($child instanceof Blank)) {
+ $newIndent = $indent > 0 ? $child->indent - $indent : 0;
+ $value .= str_repeat(' ', $newIndent) . $this->getChildValue($child, $indent);
+ }
+ $result .= $value;
+ }
+ }
+ return $result;
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/src/Nodes/LiteralFolded.php b/classes/vendor/81x/dallgoot/yaml/src/Nodes/LiteralFolded.php
new file mode 100644
index 0000000..ee56860
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/src/Nodes/LiteralFolded.php
@@ -0,0 +1,43 @@
+
+ * @license Apache 2.0
+ * @link https://github.com/dallgoot/yaml
+ */
+class LiteralFolded extends Literals
+{
+ /**
+ * @todo Example 6.1. Indentation Spaces spaces must be considered as content,
+ * Whend indent is reduced : do we insert a line break too ?
+ */
+ public function getFinalString(NodeList $value, ?int $refIndent = null): string
+ {
+ $result = '';
+ $list = $value->filterComment();
+ if ($this->identifier !== '+') {
+ self::litteralStripLeading($list);
+ self::litteralStripTrailing($list);
+ }
+ if ($list->count()) {
+ $refSeparator = ' ';
+ $first = $list->shift();
+ $indent = $refIndent ?? $first->indent;
+ $result = $this->getChildValue($first, $indent);
+ foreach ($list as $child) {
+ $separator = ($result && $result[-1] === "\n") ? '' : $refSeparator;
+ if ($child->indent > $indent || $child instanceof Blank) {
+ $separator = "\n";
+ }
+ $result .= $separator . $this->getChildValue($child, $indent);
+ }
+ }
+ return $result;
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/src/Nodes/Partial.php b/classes/vendor/81x/dallgoot/yaml/src/Nodes/Partial.php
new file mode 100644
index 0000000..9342d10
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/src/Nodes/Partial.php
@@ -0,0 +1,47 @@
+
+ * @license Apache 2.0
+ * @link https://github.com/dallgoot/yaml
+ */
+class Partial extends NodeGeneric
+{
+ /**
+ * What first character to determine if escaped sequence are allowed
+ *
+ * @param NodeGeneric $current The current
+ * @param array $emptyLines The empty lines
+ *
+ * @return boolean true to skip normal Loader process, false to continue
+ */
+ public function specialProcess(NodeGeneric &$current, array &$emptyLines): bool
+ {
+ $parent = $this->getParent();
+ $addValue = ltrim($current->raw);
+ $separator = ' ';
+ if ($this->raw[-1] === ' ' || $this->raw[-1] === "\n") {
+ $separator = '';
+ }
+ if ($current instanceof Blank) {
+ $addValue = "\n";
+ $separator = '';
+ }
+ $node = NodeFactory::get($this->raw . $separator . $addValue, $this->line);
+ $node->indent = null;
+ $parent->value = null;
+ $parent->add($node);
+ return true;
+ }
+
+ public function build(&$parent = null)
+ {
+ throw new \ParseError("Partial value found at line $this->line", 1);
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/src/Nodes/Quoted.php b/classes/vendor/81x/dallgoot/yaml/src/Nodes/Quoted.php
new file mode 100644
index 0000000..b6ecaa9
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/src/Nodes/Quoted.php
@@ -0,0 +1,20 @@
+
+ * @license Apache 2.0
+ * @link https://github.com/dallgoot/yaml
+ */
+class Quoted extends NodeGeneric
+{
+ public function build(&$parent = null)
+ {
+ // return substr(Scalar::replaceSequences(trim($this->raw)), 1,-1);
+ return (new Scalar('', 0))->replaceSequences(substr(trim($this->raw), 1, -1));
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/src/Nodes/Root.php b/classes/vendor/81x/dallgoot/yaml/src/Nodes/Root.php
new file mode 100644
index 0000000..7c5e478
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/src/Nodes/Root.php
@@ -0,0 +1,61 @@
+
+ * @license Apache 2.0
+ * @link https://github.com/dallgoot/yaml
+ */
+class Root extends NodeGeneric
+{
+ private ?YamlObject $_yamlObject = null;
+
+ public $value;
+
+ public function __construct()
+ {
+ $this->value = new NodeList();
+ }
+
+ public function getParent(?int $indent = null, $type = 0): NodeGeneric
+ {
+ if ($this->_parent !== null) {
+ throw new \ParseError(__CLASS__ . " can NOT have a parent, something's wrong", 1);
+ }
+ return $this;
+ }
+
+ public function getRoot(): Root
+ {
+ return $this;
+ }
+
+ public function getYamlObject(): YamlObject
+ {
+ if ($this->_yamlObject) {
+ return $this->_yamlObject;
+ }
+ throw new \Exception("YamlObject has not been set yet", 1);
+ }
+
+ public function build(&$parent = null): YamlObject
+ {
+ return $this->buildFinal($parent);
+ }
+
+ private function buildFinal(YamlObject $yamlObject): YamlObject
+ {
+ $this->_yamlObject = $yamlObject;
+ $this->value->setIteratorMode(NodeList::IT_MODE_DELETE);
+ foreach ($this->value as $key => $child) {
+ $child->build($yamlObject);
+ }
+ return $yamlObject;
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/src/Nodes/Scalar.php b/classes/vendor/81x/dallgoot/yaml/src/Nodes/Scalar.php
new file mode 100644
index 0000000..d405a3b
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/src/Nodes/Scalar.php
@@ -0,0 +1,146 @@
+
+ * @license Apache 2.0
+ * @link https://github.com/dallgoot/yaml
+ */
+class Scalar extends NodeGeneric
+{
+ public function __construct(string $nodeString, int $line)
+ {
+ parent::__construct($nodeString, $line);
+ $value = trim($nodeString);
+ if ($value !== '') {
+ $hasComment = strpos($value, ' #');
+ if (!is_bool($hasComment)) {
+ $realValue = trim(substr($value, 0, $hasComment));
+ $commentValue = trim(substr($value, $hasComment));
+ $realNode = NodeFactory::get($realValue, $line);
+ $realNode->indent = null;
+ $commentNode = NodeFactory::get($commentValue, $line);
+ $commentNode->indent = null;
+ $this->add($realNode);
+ $this->add($commentNode);
+ }
+ }
+ }
+
+ public function build(&$parent = null)
+ {
+ if (!is_null($this->tag)) {
+ $tagged = TagFactory::transform($this->tag, $this);
+ if ($tagged instanceof NodeGeneric || $tagged instanceof NodeList) {
+ return $tagged->build();
+ }
+ return $tagged;
+ }
+ return is_null($this->value) ? $this->getScalar(trim($this->raw)) : $this->value->build();
+ }
+
+ public function getTargetOnLessIndent(NodeGeneric &$node): NodeGeneric
+ {
+ if ($node instanceof Scalar || $node instanceof Blank) {
+ return $this->getParent();
+ } else {
+ return $this->getParent($node->indent);
+ }
+ }
+
+ public function getTargetOnMoreIndent(NodeGeneric &$node): NodeGeneric
+ {
+ return $this->getParent();
+ }
+
+
+
+ /**
+ * Returns the correct PHP type according to the string value
+ *
+ * @param string $v a string value
+ *
+ * @return mixed The value with appropriate PHP type
+ * @throws \Exception if it happens in Regex::isDate or Regex::isNumber
+ * @todo implement date as DateTime Object
+ */
+ public function getScalar(string $v, bool $onlyScalar = false)
+ {
+ /*
+ 10.3.2. Tag Resolution
+
+The core schema tag resolution is an extension of the JSON schema tag resolution.
+
+All nodes with the “!” non-specific tag are resolved, by the standard convention, to “tag:yaml.org,2002:seq”, “tag:yaml.org,2002:map”, or “tag:yaml.org,2002:str”, according to their kind.
+
+Collections with the “?” non-specific tag (that is, untagged collections) are resolved to “tag:yaml.org,2002:seq” or “tag:yaml.org,2002:map” according to their kind.
+
+Scalars with the “?” non-specific tag (that is, plain scalars) are matched with an extended list of regular expressions. However, in this case, if none of the regular expressions matches, the scalar is resolved to tag:yaml.org,2002:str (that is, considered to be a string).
+ Regular expression Resolved to tag
+ null | Null | NULL | ~ tag:yaml.org,2002:null
+ Empty tag:yaml.org,2002:null
+ true | True | TRUE | false | False | FALSE tag:yaml.org,2002:bool
+ [-+]? [0-9]+ tag:yaml.org,2002:int (Base 10)
+ 0o [0-7]+ tag:yaml.org,2002:int (Base 8)
+ 0x [0-9a-fA-F]+ tag:yaml.org,2002:int (Base 16)
+ [-+]? ( \. [0-9]+ | [0-9]+ ( \. [0-9]* )? ) ( [eE] [-+]? [0-9]+ )? tag:yaml.org,2002:float (Number)
+ [-+]? ( \.inf | \.Inf | \.INF ) tag:yaml.org,2002:float (Infinity)
+ \.nan | \.NaN | \.NAN tag:yaml.org,2002:float (Not a number)
+ * tag:yaml.org,2002:str (Default)
+ */
+ if (Regex::isDate($v)) return ($this->getRoot()->getYamlObject()->getOptions() & Loader::NO_OBJECT_FOR_DATE) && !$onlyScalar ? date_create($v) : $v;
+ if (Regex::isNumber($v)) return $this->getNumber($v);
+ $types = [
+ 'yes' => true,
+ 'no' => false,
+ 'true' => true,
+ 'false' => false,
+ 'null' => null,
+ '.inf' => \INF,
+ '-.inf' => -\INF,
+ '.nan' => \NAN
+ ];
+ return array_key_exists(strtolower($v), $types) ? $types[strtolower($v)] : $this->replaceSequences($v);
+ }
+
+ public function replaceSequences(string $value = ''): string
+ {
+ $replaceUnicodeSeq = function ($matches) {
+ return json_decode('"' . $matches[1] . '"');
+ };
+ $replaceNonPrintable = function ($matches) {
+ return $matches[1] . "";
+ };
+ return preg_replace_callback_array(
+ [
+ '/((? $replaceUnicodeSeq,
+ '/((? $replaceUnicodeSeq,
+ '/(\\\\b|\\\\n|\\\\t|\\\\r)/' => $replaceNonPrintable
+ ],
+ $value
+ );
+ }
+
+
+ /**
+ * Returns the correct PHP type according to the string value
+ *
+ * @return int|float The scalar value with appropriate PHP type
+ * @todo or scientific notation matching the regular expression -? [1-9] ( \. [0-9]* [1-9] )? ( e [-+] [1-9] [0-9]* )?
+ */
+ private function getNumber(string $v)
+ {
+ if ((bool) preg_match(Regex::OCTAL_NUM, $v)) return intval(base_convert($v, 8, 10));
+ if ((bool) preg_match(Regex::HEX_NUM, $v)) return intval(base_convert($v, 16, 10));
+ return is_bool(strpos($v, '.')) || substr_count($v, '.') > 1 ? intval($v) : floatval($v);
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/src/Nodes/SetKey.php b/classes/vendor/81x/dallgoot/yaml/src/Nodes/SetKey.php
new file mode 100644
index 0000000..e689f4b
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/src/Nodes/SetKey.php
@@ -0,0 +1,47 @@
+
+ * @license Apache 2.0
+ * @link https://github.com/dallgoot/yaml
+ */
+class SetKey extends NodeGeneric
+{
+ public function __construct(string $nodeString, int $line)
+ {
+ parent::__construct($nodeString, $line);
+ $v = substr(trim($nodeString), 1);
+ if (!empty($v)) {
+ $value = NodeFactory::get($v, $line);
+ $value->indent = null;
+ $this->add($value);
+ }
+ }
+
+ /**
+ * @param object $parent The parent
+ *
+ * @throws \Exception if a problem occurs during serialisation (json format) of the key
+ */
+ public function build(&$parent = null)
+ {
+ $built = is_null($this->value) ? null : $this->value->build();
+ $stringKey = is_string($built) && Regex::isProperlyQuoted($built) ? trim($built, '\'" ') : $built;
+ $key = json_encode($stringKey, JSON_PARTIAL_OUTPUT_ON_ERROR | JSON_UNESCAPED_SLASHES);
+ if (empty($key)) throw new \Exception("Cant serialize complex key: " . var_export($this->value, true));
+ $parent->{trim($key, '\'" ')} = null;
+ return null;
+ }
+
+ public function isAwaitingChild(NodeGeneric $child): bool
+ {
+ return is_null($this->value);
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/src/Nodes/SetValue.php b/classes/vendor/81x/dallgoot/yaml/src/Nodes/SetValue.php
new file mode 100644
index 0000000..7b8250a
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/src/Nodes/SetValue.php
@@ -0,0 +1,44 @@
+
+ * @license Apache 2.0
+ * @link https://github.com/dallgoot/yaml
+ */
+class SetValue extends NodeGeneric
+{
+ public function __construct(string $nodeString, int $line)
+ {
+ parent::__construct($nodeString, $line);
+ $v = substr(ltrim($nodeString), 1);
+ if (!empty($v)) {
+ $value = NodeFactory::get($v, $line);
+ $value->indent = null;
+ $this->add($value);
+ }
+ }
+
+ /**
+ * Builds a set value.
+ *
+ * @param object $parent The parent (the document object or any previous object created through a mapping key)
+ */
+ public function build(&$parent = null)
+ {
+ $prop = array_keys(get_object_vars($parent));
+ $key = end($prop);
+ $parent->{$key} = is_null($this->value) ? null : $this->value->build();
+ return null;
+ }
+
+ public function isAwaitingChild(NodeGeneric $node): bool
+ {
+ return is_null($this->value) || $this->getDeepestNode()->isAwaitingChild($node);
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/src/Nodes/Tag.php b/classes/vendor/81x/dallgoot/yaml/src/Nodes/Tag.php
new file mode 100644
index 0000000..eeeb8ae
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/src/Nodes/Tag.php
@@ -0,0 +1,70 @@
+
+ * @license Apache 2.0
+ * @link https://github.com/dallgoot/yaml
+ * @todo handle tags like
+ */
+class Tag extends Actions
+{
+
+ public function isAwaitingChild(NodeGeneric $node): bool
+ {
+ return is_null($this->value);
+ }
+
+ public function getTargetOnEqualIndent(NodeGeneric &$node): NodeGeneric
+ {
+ if (is_null($this->value) && $this->indent > 0) {
+ return $this;
+ } else {
+ return $this->getParent();
+ }
+ }
+
+ /**
+ * Builds a tag and its value (also built) and encapsulates them in a Tag object.
+ *
+ * @param array|object|null $parent The parent
+ *
+ * @return Tagged|mixed The tag object of class Dallgoot\Yaml\Types\Tagged.
+ */
+ public function build(&$parent = null)
+ {
+ if (is_null($this->value) && $this->getParent() instanceof Root) {
+ if (!preg_match(Regex::TAG_PARTS, (string) $this->tag, $matches)) {
+ throw new \UnexpectedValueException("Tag '$this->tag' is invalid", 1);
+ }
+ $handle = $matches['handle'];
+ $tagname = $matches['tagname'];
+ $this->getRoot()->getYamlObject()->addTag($handle, $tagname);
+ return;
+ }
+ $value = $this->value;
+ if (is_null($parent) && $value instanceof NodeGeneric && $value->isOneOf('Item', 'Key')) {
+ $value = new NodeList(
+ /** @scrutinizer ignore-type */
+ $value
+ );
+ }
+ try {
+ $transformed = TagFactory::transform((string) $this->tag, $value, $parent);
+ return $transformed;
+ } catch (\UnexpectedValueException $e) {
+ return new Tagged((string) $this->tag, is_null($value) ? null : $this->value->build($parent));
+ } catch (\Throwable $e) {
+ throw new \Exception("Tagged value could not be transformed for tag '$this->tag'", 1, $e);;
+ }
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/src/Regex.php b/classes/vendor/81x/dallgoot/yaml/src/Regex.php
new file mode 100644
index 0000000..114d805
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/src/Regex.php
@@ -0,0 +1,116 @@
+
+ * @license Apache 2.0
+ * @link https://github.com/dallgoot/yaml
+ */
+class Regex
+{
+ const OCTAL_NUM = "/^0o\d+$/i";
+ const HEX_NUM = "/^0x[\da-f]+$/i";
+ const BIN_NUM = "/^0b[01]+$/i";
+
+ const QUOTED = "(?'quot'(?'q'['\"]).*?(? baz
+ // !
+ const TAG_URI = "(?'url'tag:\\w+\\.\\w{2,},\\d{4}:\\w*)";
+ const TAG_PARTS = "/(?'handle'!(?:[\\w\\d\\-_]!|!)*)(?'tagname'(?:?)?/i";
+ const DIRECTIVE_TAG = "/(?(DEFINE)".Regex::TAG_URI.")%TAG +(?'handle'![\\w\\d\-_]+!|!!|!) +(?'uri'(?&url)|(?'prefix'![\\w\\d\-_]+))/i";
+ const DIRECTIVE_VERSION = "/%YAML *:? *(?'version'1\\.\\d)/i";
+
+
+ /**
+ * Determines if a valid Date format
+ * @param string $v a string value
+ * @return bool
+ * @throws \Exception if any preg_match has invalid regex
+ * @todo : support other date formats ???
+ */
+ public static function isDate(string $v):bool
+ {
+ $d = "\\d{4}([-\\/])\\d{2}\\1\\d{2}";
+ $h = "\\d{2}(:)\\d{2}\\2\\d{2}";
+ $date = "/^$d$/"; // 2002-12-14, 2002/12/14
+ $canonical = "/^$d(?:t| )$h\\.\\dz?$/im"; // 2001-12-15T02:59:43.1Z
+ $spaced = "/^$d(?:t| )$h\\.\\d{2} [-+]\\d$/im"; // 2001-12-14 21:59:43.10 -5
+ $iso8601 = "/^$d(?:t| )$h\\.\\d{2}[-+]\\d{2}\\2\\d{2}/im"; // 2001-12-14t21:59:43.10-05:00
+ $matchDate = preg_match($date, $v);
+ $matchCanonical = preg_match($canonical, $v);
+ $matchSpaced = preg_match($spaced, $v);
+ $matchIso = preg_match($iso8601, $v);
+
+ return $matchDate || $matchCanonical || $matchSpaced || $matchIso;
+ }
+
+ /**
+ * Determines if number.
+ *
+ * @param string $var A string value
+ *
+ * @return boolean True if number, False otherwise.
+ */
+ public static function isNumber(string $var):bool
+ {
+ // && (bool) preg_match("/^((0o\d+)|(0x[\da-f]+)|([\d.]+e[-+]\d{1,2})|([-+]?(\d*\.?\d+)))$/i", $var);
+ return is_numeric($var)
+ || (bool) preg_match(Regex::OCTAL_NUM, $var)
+ || (bool) preg_match(Regex::HEX_NUM, $var)
+ || (bool) preg_match(Regex::BIN_NUM, $var);
+ }
+
+ /**
+ * Determines if properly quoted.
+ *
+ * @param string $var The variable
+ *
+ * @return boolean True if properly quoted, False otherwise.
+ */
+ public static function isProperlyQuoted(string $var):bool
+ {
+ return (bool) preg_match("/^(['\"]).*?(?
+ * @license Apache 2.0
+ * @link https://github.com/dallgoot/yaml
+ */
+class CoreSchema implements SchemaInterface
+{
+ const SCHEMA_URI = 'tag:yaml.org,2002:';
+ const BUILDING_NAMESPACE = "\\";
+
+ private const ERROR_SET = 'Error : tag ' . self::class . ":'set' can NOT be a single Node : must be a NodeList";
+ private const ERROR_OMAP = 'Error : tag ' . self::class . ":'omap' MUST have Nodes\Item *with* a Nodes\Key";
+
+ public function __call($name, $arguments)
+ {
+ if (array_key_exists($name, get_class_methods(self::class))) {
+ return call_user_func_array([self::class, $name], $arguments);
+ } else {
+ throw new \UnexpectedValueException("ERROR: this tag '$name' is no recognised in Yaml tag Core schema, there's no way to handle it", 1);
+ }
+ }
+
+
+ /**
+ * Specific handler for 'inline' tag
+ *
+ * @param object $node
+ * @param object|array|null $parent The parent
+ *
+ * @todo REMOVE ME : no traces found on yaml.org reference
+ */
+ // public function inline(object $node, &$parent = null)
+ // {
+ // return $this->str($node, $parent);
+ // }
+
+ /**
+ * Specific Handler for 'str' tag
+ *
+ * @param NodeGeneric|NodeList $node The Node or NodeList
+ * @param object|array|null $parent The parent
+ *
+ * @return string the value of Node converted to string if needed
+ */
+ public function str($node, &$parent = null): ?string
+ {
+ if ($node instanceof Literals) {
+ $node = $node->value;
+ }
+ if ($node instanceof NodeGeneric) {
+ $value = trim($node->raw);
+ if ($node instanceof Nodes\Quoted) {
+ $value = $node->build();
+ }
+ return $value;
+ } elseif ($node instanceof NodeList) {
+ $list = [];
+ foreach ($node as $key => $child) {
+ $list[] = $this->str($child);
+ }
+ // return new Nodes\Scalar(implode('',$list), 0);
+ return implode('', $list);
+ }
+ }
+
+ /**
+ * Specific Handler for 'binary' tag
+ *
+ * @param object $node The node or NodeList
+ *
+ * @return string The value considered as 'binary' Note: the difference with strHandler is that multiline have not separation
+ */
+ public function binary($node, NodeGeneric &$parent = null)
+ {
+ return $this->str($node, $parent);
+ }
+
+ /**
+ * Specific Handler for the '!set' tag
+ *
+ * @param object $node The node
+ * @param object|array|null $parent The parent
+ *
+ * @throws \Exception if theres a set but no children (set keys or set values)
+ * @return mixed process the Set, ie. an object construction with properties as serialized JSON values
+ */
+ public function set($node, &$parent = null)
+ {
+ if (!($node instanceof NodeList)) {
+ throw new \LogicException(self::ERROR_SET);
+ } else {
+ $list = $parent ?? new \stdClass;
+ $node->rewind();
+ foreach ($node as $key => $item) {
+ $this->omap($item, $list);
+ $list->{$item->value->build()} = null;
+ }
+ if (!$parent) {
+ return $list;
+ }
+ }
+ }
+
+ /**
+ * Specific Handler for the 'omap' tag
+ *
+ * @param object $node The node
+ * @param object|array|null $parent The parent
+ *
+ * @throws \Exception if theres an omap but no map items
+ * @return mixed process the omap
+ */
+ public function omap($node, &$parent = null)
+ {
+ if ($node instanceof NodeGeneric) {
+ if ($node instanceof Nodes\Item && $node->value instanceof Nodes\Key) {
+ $key = $node->value;
+ $keyName = $key->identifier;
+ $keyValue = $key->value->build();
+ if (is_null($parent)) {
+ return [$keyName => $keyValue];
+ } else {
+ $parent[$keyName] = $keyValue;
+ }
+ } else {
+ throw new \UnexpectedValueException(self::ERROR_OMAP);
+ }
+ } elseif ($node instanceof NodeList) {
+ //verify that each child is an item with a key as child
+ $list = $parent ?? [];
+ $node->rewind();
+ foreach ($node as $key => $item) {
+ $this->omap($item, $list);
+ }
+ if (!$parent) {
+ return $list;
+ }
+ }
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/src/Tag/SchemaInterface.php b/classes/vendor/81x/dallgoot/yaml/src/Tag/SchemaInterface.php
new file mode 100644
index 0000000..ca3ae9f
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/src/Tag/SchemaInterface.php
@@ -0,0 +1,27 @@
+
+ * @license Apache 2.0
+ * @link https://github.com/dallgoot/yaml
+ *
+ */
+interface SchemaInterface
+{
+ /** @var string */
+ // const SCHEMA_URI = null;
+ // /** @var string */
+ // const BUILDING_NAMESPACE = null;
+
+ /**
+ * When the tag is not a method on the SchemaClass provide the logic to handle it
+ */
+ public function __call($name, $arguments);
+}
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/src/Tag/SymfonySchema.php b/classes/vendor/81x/dallgoot/yaml/src/Tag/SymfonySchema.php
new file mode 100644
index 0000000..e701388
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/src/Tag/SymfonySchema.php
@@ -0,0 +1,51 @@
+
+ * @license Apache 2.0
+ * @link https://github.com/dallgoot/yaml
+ */
+class SymfonySchema implements SchemaInterface
+{
+ const SCHEMA_URI = 'tag:symfony.com,2019:';
+ const BUILDING_NAMESPACE = "\\Symfony\\Component";
+
+ /**
+ *
+ * Specific Handler for Symfony custom tag : 'php/object'
+ *
+ *
+ * @throws \Exception if unserialize fails OR if its a NodeList (no support of multiple values for this tag)
+ */
+ public final static function PHPobjectHandler(object $node): object
+ {
+ if ($node instanceof Scalar) {
+ $phpObject = unserialize($node->raw);
+ // NOTE : we assume this is only used for Object types (if a boolean false is serialized this will FAIL)
+ if (is_bool($phpObject)) {
+ throw new \Exception("value for tag 'php/object' could NOT be unserialized");
+ }
+ return $phpObject;
+ } elseif ($node instanceof NodeList) {
+ throw new \Exception("tag 'php/object' can NOT be a NodeList");
+ }
+ }
+
+ public function __call($name, $arguments)
+ {
+ //TODO : handle 'php/object'
+ if(in_array($name, ['php/object'])) {
+ return match($name) {
+ 'php/object' => self::PHPobjectHandler($arguments),
+ default => null,
+ };
+ }
+ throw new \Exception("no handler for tag '$name' in " . self::class, 1);
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/src/Tag/TagFactory.php b/classes/vendor/81x/dallgoot/yaml/src/Tag/TagFactory.php
new file mode 100644
index 0000000..267d75b
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/src/Tag/TagFactory.php
@@ -0,0 +1,146 @@
+
+ * @license Apache 2.0
+ * @link https://github.com/dallgoot/yaml
+ *
+ * @todo move legacy tags handlers in a specific class : checking affecting methods to tags mechanisms when theres a global tag
+ */
+class TagFactory
+{
+ private const UNKNOWN_TAG = 'Error: tag "%s" is unknown (have you registered a handler for it? see Dallgoot\Yaml\Tag\SchemaInterface)';
+ private const NO_NAME = '%s Error: a tag MUST have a name';
+ private const WRONG_VALUE = "Error : cannot transform tag '%s' for type '%s'";
+ private const ERROR_HANDLE_EXISTS = "This tag handle is already registered, did you use a named handle like '!name!' ?";
+
+ public static $schemas = [];
+ public static $schemaHandles = [];
+ /**
+ The primary tag handle is a single “!” character.
+ # Global
+%TAG ! tag:example.com,2000:app/
+---
+!foo "bar"
+
+ The secondary tag handle is written as “!!”. This allows using a compact notation for a single “secondary” name space. By default, the prefix associated with this handle is “tag:yaml.org,2002:”. This prefix is used by the YAML tag repository.
+ %TAG !! tag:example.com,2000:app/
+---
+!!int 1 - 3 # Interval, not integer
+
+
+Named Handles
+
+ A named tag handle surrounds a non-empty name with “!” characters. A handle name must not be used in a tag shorthand unless an explicit “TAG” directive has associated some prefix with it.
+
+ The name of the handle is a presentation detail and must not be used to convey content information. In particular, the YAML processor need not preserve the handle name once parsing is completed.
+
+
+
+Verbatim Tags
+ A tag may be written verbatim by surrounding it with the “<” and “>” characters. In this case, the YAML processor must deliver the verbatim tag as-is to the application. In particular, verbatim tags are not subject to tag resolution. A verbatim tag must either begin with a “!” (a local tag) or be a valid URI (a global tag).
+
+
+ ! baz
+
+
+%TAG !e! tag:example.com,2000:app/
+---
+!e!foo "bar"
+
+%TAG ! tag:example.com,2000:app/
+%TAG !! tag:example.com,2000:app/
+%TAG !e! tag:example.com,2000:app/
+! foo :
+
+ */
+ /**
+ * Add Handlers for legacy Yaml tags
+ *
+ * @see self::LEGACY_TAGS_HANDLERS
+ * @todo remove dependency to ReflectionClass using 'get_class_methods'
+ */
+ private static function createCoreSchema()
+ {
+ $coreSchema = new CoreSchema;
+ self::registerSchema($coreSchema::SCHEMA_URI, $coreSchema);
+ self::registerHandle("!!", $coreSchema::SCHEMA_URI);
+ }
+
+ public static function registerSchema($URI, SchemaInterface $schemaObject)
+ {
+ self::$schemas[$URI] = $schemaObject;
+ }
+
+ public static function registerHandle(string $handle, string $prefixOrURI)
+ {
+ if (array_key_exists($handle, self::$schemaHandles)) {
+ throw new \Exception(self::ERROR_HANDLE_EXISTS, 1);
+ }
+ self::$schemaHandles[$handle] = $prefixOrURI;
+ }
+
+ /**
+ * transform a Node type based on the tag ($identifier) provided
+ *
+ * @param string $identifier The identifier
+ * @param mixed $value The value
+ *
+ * @throws \Exception Raised if the Tag $identifier is unknown (= not in TagDefault nor registered by user)
+ *
+ * @return mixed If $value can be preserved as Node type :the same Node type,
+ * otherwise any type that the tag transformation returns
+ */
+ public static function transform(string $identifier, $value, &$parent = null)
+ {
+ if (count(self::$schemas) === 0) {
+ self::createCoreSchema();
+ }
+ if (!($value instanceof NodeGeneric) && !($value instanceof NodeList)) {
+ throw new \Exception(sprintf(self::WRONG_VALUE, $identifier, gettype($value)));
+ } else {
+ // try {
+ if (!preg_match(Regex::TAG_PARTS, $identifier, $matches)) {
+ throw new \UnexpectedValueException("Tag '$identifier' is invalid", 1);
+ }
+ return self::runHandler(
+ $matches['handle'],
+ $matches['tagname'],
+ $value,
+ $parent
+ );
+ // } catch (\UnexpectedValueException $e) {
+ // return new Tagged($identifier, is_null($value) ? null : $value->build($parent));
+ // } catch (\Throwable $e) {
+ // throw new \Exception("Tagged value could not be transformed for tag '$identifier'", 1, $e);;
+ // }
+ }
+ }
+
+ public static function runHandler($handle, $tagname, $value, &$parent = null)
+ {
+ if (array_key_exists($handle, self::$schemaHandles)) {
+ $schemaName = self::$schemaHandles[$handle];
+ if (array_key_exists($schemaName, self::$schemas)) {
+ $schemaObject = self::$schemas[$schemaName];
+ if (is_object($schemaObject) && is_string($tagname)) {
+ return $schemaObject->{$tagname}($value, $parent);
+ }
+ }
+ }
+ throw new \UnexpectedValueException("Error Processing tag '$tagname' : in $handle", 1);
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/src/Tag/notes.yml b/classes/vendor/81x/dallgoot/yaml/src/Tag/notes.yml
new file mode 100644
index 0000000..c3d2c32
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/src/Tag/notes.yml
@@ -0,0 +1,36 @@
+TAGS NOTES:
+
+
+3.2.1.2. Tags : |
+
+ YAML represents type information of native data structures with a simple
+ identifier, called a tag. Global tags are URIs and hence globally unique
+ across all applications. The “tag:” URI scheme is recommended for all
+ global YAML tags. In contrast, local tags are specific to a single
+ application. Local tags start with “!”, are not URIs and are not expected
+ to be globally unique. YAML provides a “TAG” directive to make tag
+ notation less verbose; it also offers easy migration from local to
+ global tags. To ensure this, local tags are restricted to the
+ URI character set and use URI character escaping.
+
+ YAML does not mandate any special relationship between different tags
+ that begin with the same substring. Tags ending with URI fragments
+ (containing “#”) are no exception; tags that share the same base URI
+ but differ in their fragment part are considered to be different,
+ independent tags. By convention, fragments are used to identify
+ different “variants” of a tag, while “/” is used to define nested
+ tag “namespace” hierarchies. However, this is merely a convention,
+ and each tag may employ its own rules.
+ For example, Perl tags may use “::” to express namespace hierarchies,
+ Java tags may use “.”, etc.
+
+ YAML tags are used to associate meta information with each node.
+ In particular, each tag must specify the expected node kind
+ (scalar, sequence, or mapping). Scalar tags must also provide a
+ mechanism for converting formatted content to a canonical form for
+ supporting equality testing. Furthermore, a tag may provide additional
+ information such as the set of allowed content values for validation,
+ a mechanism for tag resolution, or any other data that is applicable
+ to all of the tag’s nodes.
+
+
diff --git a/classes/vendor/81x/dallgoot/yaml/src/Types/Compact.php b/classes/vendor/81x/dallgoot/yaml/src/Types/Compact.php
new file mode 100644
index 0000000..901777b
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/src/Types/Compact.php
@@ -0,0 +1,39 @@
+
+ * @license Apache 2.0
+ * @link https://github.com/dallgoot/yaml
+ */
+#[\AllowDynamicProperties]
+class Compact extends \ArrayIterator implements \JsonSerializable
+{
+ /**
+ * Construct Compact according to argument if present
+ *
+ * @param array|object $candidate The candidate to be made into Compact
+ */
+ public function __construct($candidate = null)
+ {
+ $candidate = $candidate ?? [];
+ //ArrayIterator options --> Array indices can be accessed as properties in read/write.
+ parent::__construct(
+ /** @scrutinizer ignore-type */
+ $candidate, \ArrayIterator::STD_PROP_LIST | \ArrayIterator::ARRAY_AS_PROPS);
+ }
+
+ /**
+ * Provides the correct ouput for Json Serialization
+ *
+ * @return array
+ */
+ public function jsonSerialize(): array
+ {
+ $prop = get_object_vars($this);
+ return count($prop) > 0 ? $prop : iterator_to_array($this);
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/src/Types/Tagged.php b/classes/vendor/81x/dallgoot/yaml/src/Types/Tagged.php
new file mode 100644
index 0000000..979dd15
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/src/Types/Tagged.php
@@ -0,0 +1,48 @@
+
+ * @license Apache 2.0
+ * @link https://github.com/dallgoot/yaml
+ * @see TagFactory
+ */
+final class Tagged
+{
+ public string $tagName;
+ public mixed $value;
+
+ private const NO_NAME = '%s Error: a tag MUST have a name';
+
+ public function __construct(string $tagName, mixed $value)
+ {
+ if (empty($tagName)) {
+ throw new \Exception(sprintf(self::NO_NAME, __METHOD__));
+ }
+ $this->tagName = $tagName;
+ $this->value = $value;
+ }
+
+ /**
+ * Should verify if the tag is correct
+ *
+ * @param string $providedName The provided name
+ * @todo is this required ???
+ */
+ // private function checkNameValidity(string $providedName)
+ // {
+ /* TODO implement and throw Exception if invalid (setName method ???)
+ *The suffix must not contain any “!” character. This would cause the tag shorthand to be interpreted as having a named tag handle. In addition, the suffix must not contain the “[”, “]”, “{”, “}” and “,” characters. These characters would cause ambiguity with flow collection structures. If the suffix needs to specify any of the above restricted characters, they must be escaped using the “%” character. This behavior is consistent with the URI character escaping rules (specifically, section 2.3 of RFC2396).
+
+ regex (([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
+ */
+ // }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/src/Types/YamlObject.php b/classes/vendor/81x/dallgoot/yaml/src/Types/YamlObject.php
new file mode 100644
index 0000000..502f2c9
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/src/Types/YamlObject.php
@@ -0,0 +1,196 @@
+
+ * @license Apache 2.0
+ * @link https://github.com/dallgoot/yaml
+ */
+#[\AllowDynamicProperties]
+class YamlObject extends \ArrayIterator implements \JsonSerializable
+{
+ private YamlProperties $__yaml__object__api;
+
+ const UNDEFINED_METHOD = self::class . ": undefined method '%s', valid methods are (addReference,getReference,getAllReferences,addComment,getComment,setText,addTag,hasDocStart,isTagged)";
+ const UNKNOWN_REFERENCE = "no reference named: '%s', known are : (%s)";
+ const UNAMED_REFERENCE = "reference MUST have a name";
+ const TAGHANDLE_DUPLICATE = "Tag handle '%s' already declared before, handle must be unique";
+
+ /**
+ * Construct the YamlObject making sure the indices can be accessed directly
+ * and creates the API object with a reference to this YamlObject.
+ * @todo check indices access outside of foreach loop
+ */
+ public function __construct($buildingOptions)
+ {
+ parent::__construct([], 1); //1 = Array indices can be accessed as properties in read/write.
+ $this->__yaml__object__api = new YamlProperties($buildingOptions);
+ }
+
+ /**
+ * Returns a string representation of the YamlObject when
+ * it has NO property NOR keys ie. is only a LITTERAL
+ *
+ * @return string String representation of the object.
+ */
+ public function __toString(): string
+ {
+ return $this->__yaml__object__api->value ?? serialize($this);
+ }
+
+ public function getOptions()
+ {
+ return $this->__yaml__object__api->_options;
+ }
+ /**
+ * Adds a reference.
+ *
+ * @param mixed $value The reference value
+ *
+ * @throws \UnexpectedValueException (description)
+ * @return mixed
+ */
+ public function &addReference(string $name, $value)
+ {
+ if (empty($name)) {
+ throw new \UnexpectedValueException(self::UNAMED_REFERENCE);
+ }
+ $this->__yaml__object__api->_anchors[$name] = $value;
+ return $this->__yaml__object__api->_anchors[$name];
+ }
+
+ /**
+ * Return the reference saved by $name
+ *
+ * @return mixed Value of the reference
+ * @throws \UnexpectedValueException if there's no reference by that $name
+ */
+ public function &getReference(string $name)
+ {
+ if (array_key_exists($name, $this->__yaml__object__api->_anchors)) {
+ return $this->__yaml__object__api->_anchors[$name];
+ }
+ throw new \UnexpectedValueException(
+ sprintf(
+ self::UNKNOWN_REFERENCE,
+ $name,
+ implode(',', array_keys($this->__yaml__object__api->_anchors))
+ )
+ );
+ }
+
+ /**
+ * Return array with all references as Keys and their values, declared for this YamlObject
+ *
+ * @return array
+ */
+ public function getAllReferences(): array
+ {
+ return $this->__yaml__object__api->_anchors;
+ }
+
+ /**
+ * Adds a comment.
+ *
+ * @param int $lineNumber The line number at which the comment should appear
+ * @param string $value The comment
+ *
+ * @return null
+ */
+ public function addComment(int $lineNumber, string $value): void
+ {
+ $this->__yaml__object__api->_comments[$lineNumber] = $value;
+ }
+
+ /**
+ * Gets the comment at $lineNumber
+ *
+ * @return string|array The comment at $lineNumber OR all comments.
+ */
+ public function getComment(?int $lineNumber = 0)
+ {
+ if (array_key_exists((string) $lineNumber, $this->__yaml__object__api->_comments)) {
+ return $this->__yaml__object__api->_comments[$lineNumber];
+ }
+ return $this->__yaml__object__api->_comments;
+ }
+
+ /**
+ * Sets the text when the content is *only* a literal
+ */
+ public function setText(string $value): YamlObject
+ {
+ $this->__yaml__object__api->value .= ltrim($value);
+ return $this;
+ }
+
+ /**
+ * TODO: what to do with these tags ???
+ * Adds a tag.
+ *
+ * @param string $handle The handle declared for the tag
+ * @param string $prefix The prefix/namespace/schema that defines the tag
+ *
+ * @return null
+ */
+ public function addTag(string $handle, string $prefix)
+ {
+ // It is an error to specify more than one “TAG” directive for the same handle in the same document, even if both occurrences give the same prefix.
+ if (array_key_exists($handle, $this->__yaml__object__api->_tags)) {
+ throw new \Exception(sprintf(self::TAGHANDLE_DUPLICATE, $handle), 1);
+ }
+ $this->__yaml__object__api->_tags[$handle] = $prefix;
+ }
+
+ /**
+ * Determines if it has YAML document start string => '---'.
+ *
+ * @return boolean True if document has start, False otherwise.
+ */
+ public function hasDocStart(): bool
+ {
+ return is_bool($this->__yaml__object__api->_hasDocStart);
+ }
+
+ /**
+ * Sets the document start.
+ *
+ * @param null|bool $value The value : null = no docstart, true = docstart before document comments, false = docstart after document comments
+ *
+ * @return null
+ */
+ public function setDocStart($value)
+ {
+ $this->__yaml__object__api->_hasDocStart = $value;
+ }
+
+ /**
+ * Is the whole YAML document (YamlObject) tagged ?
+ *
+ * @return bool
+ */
+ public function isTagged()
+ {
+ return !empty($this->__yaml__object__api->_tags);
+ }
+
+ /**
+ * Filters unwanted property for JSON serialization
+ *
+ * @return mixed Array (of object properties or keys) OR string if YAML object only contains LITTERAL (in self::value)
+ */
+ #[\ReturnTypeWillChange]
+ public function jsonSerialize()
+ {
+ $prop = get_object_vars($this);
+ unset($prop["__yaml__object__api"]);
+ if (count($prop) > 0) return $prop;
+ if (count($this) > 0) return iterator_to_array($this);
+ return $this->__yaml__object__api->value ?? "_Empty YamlObject_";
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/src/Yaml.php b/classes/vendor/81x/dallgoot/yaml/src/Yaml.php
new file mode 100644
index 0000000..d267f72
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/src/Yaml.php
@@ -0,0 +1,121 @@
+
+ * @license Apache 2.0
+ * @link https://github.com/dallgoot/yaml
+ *
+ * @see YamlObject
+ * @see Compact
+ * @see Tag
+ */
+final class Yaml
+{
+ const VERSION_SUPPORT = "1.2";
+ /**
+ * Parse the given Yaml string to either :
+ * - a YamlObject
+ * - an array of YamlObject
+ *
+ * @param string $someYaml Some yaml
+ * @param ?int $options from Loader class, bitwise combination of
+ * Loader::IGNORE_DIRECTIVES
+ * Loader::IGNORE_COMMENTS
+ * Loader::NO_PARSING_EXCEPTIONS
+ * Loader::NO_OBJECT_FOR_DATE
+ * @param ?int $debug define the level of debugging (true = default)
+ *
+ * @return YamlObject|array|null a Yaml document as YamlObject OR multiple documents as an array of YamlObject,
+ * NULL if Error and option Loader::NO_PARSING_EXCEPTIONS is set.
+ * @throws \Exception coming from Dallgoot\Yaml\Loader
+ * @see Dallgoot\Yaml\Loader
+ *
+ * @todo transpose Loader::NO_PARSING_EXCEPTIONS in this class
+ */
+ public static function parse(string $someYaml, ?int $options = null, ?int $debug = null)
+ {
+ try {
+ return (new Loader(null, $options, $debug))->parse($someYaml);
+ } catch (\Throwable $e) {
+ throw new \Exception(__CLASS__ . " Error while parsing YAML string", 1, $e);
+ }
+ }
+
+ /**
+ * Load the given file and parse its content (assumed YAML) to either :
+ * - a YamlObject
+ * - an array of YamlObject
+ *
+ * @param string $fileName Some file path name
+ * @param ?int $options from Loader class, bitwise combination of
+ * Loader::IGNORE_DIRECTIVES
+ * Loader::IGNORE_COMMENTS
+ * Loader::NO_PARSING_EXCEPTIONS
+ * Loader::NO_OBJECT_FOR_DATE
+ * @param ?int $debug define the level of debugging (true = default)
+ *
+ * @return YamlObject|array|null a Yaml document as YamlObject OR multiple documents as an array of YamlObject,
+ * NULL if Error
+ * @throws \Exception coming from Dallgoot\Yaml\Loader
+ * @see Dallgoot\Yaml\Loader
+ */
+ public static function parseFile(string $fileName, ?int $options = null, ?int $debug = null)
+ {
+ try {
+ return (new Loader($fileName, $options, (int) $debug))->parse();
+ } catch (\Throwable $e) {
+ throw new \Exception(__CLASS__ . " Error during parsing '$fileName'", 1, $e);
+ }
+ }
+
+ /**
+ * Returns the YAML representation corresponding to given PHP variable
+ *
+ * @param mixed $somePhpVar Some php variable
+ * @param ?int $options enable/disable some options see Dumper
+ *
+ * @return string ( the representation of $somePhpVar as a YAML content (single or multiple document accordingly) )
+ * @throws \Exception on errors during building YAML string coming from Dumper class
+ * @see Dumper
+ */
+ public static function dump($somePhpVar, ?int $options = null): string
+ {
+ try {
+ return (new Dumper($options))->toString($somePhpVar);
+ } catch (\Throwable $e) {
+ throw new \Exception(__CLASS__ . " Error dumping", 1, $e);
+ }
+ }
+
+
+ /**
+ * Builds the YAML representation corresponding to given PHP variable ($somePhpVar)
+ * AND save it as file with the $fileName provided.
+ *
+ * @param string $fileName The file name
+ * @param mixed $somePhpVar Some php variable
+ * @param ?int $options Dumper::constants as options
+ *
+ * @return boolean true if YAML built and saved , false if error during writing file
+ * @throws \Exception on errors (from Dumper::toString) during building YAML string
+ * @see Dumper
+ */
+ public static function dumpFile(string $fileName, $somePhpVar, ?int $options = null): bool
+ {
+ try {
+ return (new Dumper($options))->toFile($fileName, $somePhpVar);
+ } catch (\Throwable $e) {
+ throw new \Exception(__CLASS__ . " Error during dumping '$fileName'", 1, $e);
+ }
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/src/YamlProperties.php b/classes/vendor/81x/dallgoot/yaml/src/YamlProperties.php
new file mode 100644
index 0000000..32e2e0a
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/src/YamlProperties.php
@@ -0,0 +1,35 @@
+
+ * @license Apache 2.0
+ * @link https://github.com/dallgoot/yaml
+ */
+class YamlProperties
+{
+ public ?bool $_hasDocStart = null; // null = no docstart, true = docstart before document comments, false = docstart after document comments
+
+ public array $_anchors = [];
+
+ public array $_comments = [];
+
+ public array $_tags = [];
+
+ public int $_options;
+
+ public ?string $value = null;
+
+ /**
+ * Creates API object to be used for the document provided as argument
+ *
+ * @param int $buildingOptions the YamlObject as the target for all methods call that needs it
+ */
+ public function __construct(int $buildingOptions)
+ {
+ $this->_options = $buildingOptions;
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/test_all_versions.sh b/classes/vendor/81x/dallgoot/yaml/test_all_versions.sh
new file mode 100644
index 0000000..9577057
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/test_all_versions.sh
@@ -0,0 +1,81 @@
+#!/bin/bash
+
+# docker run --rm -v $(pwd):/app -w /app composer install
+
+function displayTitle {
+ echo -e "\e[1m*** Testing with \e[92m$1\e[39m\e[0m ***"
+}
+function version_gt() {
+ test "$(printf '%s\n' "$@" | sort -V | head -n 1)" != "$1";
+}
+echo Checking Docker is running...
+docker version
+docker_status=$?
+if test $docker_status -gt 0
+then
+ echo "Error: Docker is not running correctly ???"
+ exit 1
+fi
+# i=0
+
+# while [ $? == 0 ]
+# do
+# i=$((i+1))
+# curl https://registry.hub.docker.com/v2/repositories/library/php/tags/?page=$i 2>/dev/null|jq '."results"[]["name"]'
+
+# done
+# exit
+COMPOSER_CURRENT=$(composer show 'phpunit/phpunit' | grep -Eow "[0-9\.]+" -m 1 )
+# if version_gt $COMPOSER_CURRENT "6.5"; then
+# composer remove phpunit/phpunit theseer/phpdox
+# composer require phpunit/phpunit:^6.5
+# fi
+declare -A versions
+# 7.key.value
+versions[0]=27
+versions[1]=14
+versions[2]=1
+
+
+onlyMinor=1
+
+command="vendor/bin/phpunit \
+ -c ./configuration/phpunit.xml \
+ --testsuite All --no-coverage --columns 160 --disallow-test-output"
+ # --testsuite units --no-coverage --columns 160 --disallow-test-output --no-logging"
+
+for minor in "${!versions[@]}"
+do
+ echo "********************* testing PHP 8.$minor ***************************"
+ tag="cli-alpine"
+ if test $minor -eq 0
+ then
+ tag="cli"
+ fi
+ if [ $onlyMinor != 1 ]
+ then
+ for patch in `seq 0 ${versions[$minor]}`
+ do
+ displayTitle "8.$minor.$patch"
+ docker run --rm -v $(pwd):/app -w /app php:8.$minor.$patch-$tag $command
+ done
+ else
+ patch=${versions[$minor]}
+ displayTitle "8.$minor.$patch"
+ docker run --rm -v $(pwd):/app -w /app php:8.$minor.$patch-$tag $command
+ fi
+done
+
+# php composer.phar self-update 1.7.0
+# for v in "${versions[@]}"
+# do
+# echo -e "\e[1m*** Testing with \e[92m$v\e[39m\e[0m ***"
+# docker run --rm -v $(pwd):/app -w /app php:$v vendor/bin/phpunit --configuration ./configuration/phpunit.xml --testsuite All --no-coverage --columns 160 --disallow-test-output --do-not-cache-result --no-logging
+# done
+# php composer.phar self-update 1.9.0
+#
+# if version_gt $COMPOSER_CURRENT "6.5"; then
+# composer self-update
+# composer require --dev theseer/phpdox
+# composer update phpunit/phpunit
+# fi
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/CasesTest.php b/classes/vendor/81x/dallgoot/yaml/tests/CasesTest.php
new file mode 100644
index 0000000..b2c6a92
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/CasesTest.php
@@ -0,0 +1,94 @@
+ $value) {
+ yield [$key, $value];
+ }
+ };
+ return $generator();
+ }
+
+ public static function examplesProvider()
+ {
+ $nameResultPair = get_object_vars(Yaml::parseFile(__DIR__ . '/definitions/examples_tests.yml'));
+ // $this->assertArrayHasKey('Example_2_01', $nameResultPair, 'ERROR during Yaml::parseFile for ../definitions/examples_tests.yml');
+ // $this->assertEquals(28, count($nameResultPair));
+ return self::getGenerator($nameResultPair);
+ }
+
+ public static function parsingProvider()
+ {
+ $nameResultPair = get_object_vars(Yaml::parseFile(__DIR__ . '/definitions/parsing_tests.yml'));
+ // $this->assertEquals(58, count($nameResultPair));
+ return self::getGenerator($nameResultPair);
+ }
+
+ // public function failingProvider()
+ // {
+ // $nameResultPair = get_object_vars(Yaml::parseFile(__DIR__.'/definitions/failing_tests.yml'));
+ // $this->assertEquals(20, count($nameResultPair));
+ // return $this->getGenerator($nameResultPair);
+ // }
+
+ /**
+ * @dataProvider examplesProvider
+ * @coversNothing
+ */
+ public function testBatchExamples($fileName, $expected)
+ {
+ TagFactory::$schemaHandles = [];
+ TagFactory::$schemas = [];
+ $output = Yaml::parseFile(__DIR__ . "/cases/examples/$fileName.yml");
+ $result = json_encode($output, self::JSON_OPTIONS);
+ $this->assertContains(json_last_error(), [JSON_ERROR_NONE, JSON_ERROR_INF_OR_NAN], json_last_error_msg());
+ // $this->assertEquals($expected, $result, is_array($output) ? $output[0]->getComment(1) : $output->getComment(1));
+ $this->assertEquals($expected, $result, $fileName . (is_null($output) ? "\t" . Loader::$error : ''));
+ }
+
+ /**
+ * @dataProvider failingProvider
+ */
+ // public function testBatchFailing($fileName)
+ // {
+ // $content = file_get_contents($this->testFolder."/failing/$fileName.yml");
+ // // $this->assertInstanceOf(Yaml::parse($content), new \ParseError);
+ // $this->expectException(\Exception::class);
+ // // Yaml::parse($content);
+ // }
+
+ /**
+ * @dataProvider parsingProvider
+ * @coversNothing
+ * @todo verify that every test file has a result in tests/definitions/parsing.yml
+ */
+ public function testBatchParsing($fileName, $expected)
+ {
+ TagFactory::$schemas = [];
+ TagFactory::$schemaHandles = [];
+ $yaml = file_get_contents(__DIR__ . "/cases/parsing/$fileName.yml");
+ $output = Yaml::parse($yaml, Loader::NO_PARSING_EXCEPTIONS);
+ $result = json_encode($output, self::JSON_OPTIONS);
+ $this->assertContains(json_last_error(), [JSON_ERROR_NONE, JSON_ERROR_INF_OR_NAN], json_last_error_msg());
+ $this->assertEquals($expected, $result, $fileName . (is_null($output) ? "\t" . Loader::$error : ''));
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/DumpingTest.php b/classes/vendor/81x/dallgoot/yaml/tests/DumpingTest.php
new file mode 100644
index 0000000..70b8a87
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/DumpingTest.php
@@ -0,0 +1,32 @@
+ $testResult) {
+ yield [$testName, $testResult];
+ }
+ };
+ return $generator();
+ }
+
+ /**
+ * @dataProvider dumpingCasesProvider
+ * @param string $fileName
+ * @param string $expected
+ * @coversNothing
+ */
+ public function test_DumpingCases(string $fileName, string $expected)
+ {
+ $php = (include __DIR__."/cases/dumping/$fileName.php");
+ $output = Yaml::dump($php);
+ $this->assertEquals($expected, $output, "$fileName.php");
+ }
+}
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/SymfonyYamlTest.php b/classes/vendor/81x/dallgoot/yaml/tests/SymfonyYamlTest.php
new file mode 100644
index 0000000..9de721f
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/SymfonyYamlTest.php
@@ -0,0 +1,80 @@
+ $value) {
+ yield [$key, rtrim($value)];
+ }
+ };
+ return $generator();
+ }
+
+ public function examplestestSymfonyProvider()
+ {
+ $nameResultPair = Yaml::parseFile(__DIR__.'/definitions/examples_tests.yml');
+ $this->assertArrayHasKey('Example_2_01', $nameResultPair, 'ERROR during Yaml::parseFile for ../definitions/examples_tests.yml');
+ return $this->getGenerator($nameResultPair);
+ }
+
+ public function parsingtestSymfonyProvider()
+ {
+ $nameResultPair = Yaml::parseFile(__DIR__.'/definitions/parsing_tests.yml');
+ return $this->getGenerator($nameResultPair);
+ }
+
+ public function failingtestSymfonyProvider()
+ {
+ $nameResultPair = Yaml::parseFile(__DIR__.'/definitions/failing_tests.yml');
+ return $this->getGenerator($nameResultPair);
+ }
+
+ /**
+ * @dataProvider examplesProvider
+ * @group cases
+ */
+ public function testSymfonyBatchExamples($fileName, $expected)
+ {
+ $output = Yaml::parseFile($this->testFolder.'examples/'.$fileName.'.yml');
+ $result = json_encode($output, self::JSON_OPTIONS);
+ $this->assertContains(json_last_error(), [JSON_ERROR_NONE, JSON_ERROR_INF_OR_NAN], json_last_error_msg());
+ $this->assertEquals($expected, $result);
+ }
+
+ /**
+ * @dataProvider failingtestSymfonyProvider
+ * @group cases
+ */
+ // public function testBatchFailing($fileName)
+ // {
+ // $content = file_get_contents($this->testFolder."/failing/$fileName.yml");
+ // // $this->assertInstanceOf(Y::parse($content), new \ParseError);
+ // $this->expectException(\Exception::class);
+ // // Y::parse($content);
+ // }
+
+ /**
+ * @dataProvider parsingtestSymfonyProvider
+ * @group cases
+ * @todo verify that every test file has a result in tests/definitions/parsing.yml
+ */
+ public function testSymfonyBatchParsing($fileName, $expected)
+ {
+ $yaml = file_get_contents($this->testFolder."parsing/$fileName.yml");
+ $output = Yaml::parse($yaml, Yaml::PARSE_CUSTOM_TAGS);
+ $result = json_encode($output, self::JSON_OPTIONS);
+ $this->assertContains(json_last_error(), [JSON_ERROR_NONE, JSON_ERROR_INF_OR_NAN], json_last_error_msg());
+ $this->assertEquals($expected, $result);
+ }
+}
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/array.php b/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/array.php
new file mode 100644
index 0000000..a70877e
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/array.php
@@ -0,0 +1,3 @@
+ 'alphakey_value', 'a', 'b', 'c'];
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/compact_array.php b/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/compact_array.php
new file mode 100644
index 0000000..85d043a
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/compact_array.php
@@ -0,0 +1,11 @@
+key1 = new Compact([1,2,3]);
+
+
+return $o;
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/compact_array_in_object.php b/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/compact_array_in_object.php
new file mode 100644
index 0000000..fce3445
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/compact_array_in_object.php
@@ -0,0 +1,15 @@
+array = [1,2,3];
+
+$yaml->key1 = new Compact($o);
+
+return $yaml;
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/compact_object.php b/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/compact_object.php
new file mode 100644
index 0000000..f4c1717
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/compact_object.php
@@ -0,0 +1,17 @@
+key1 = 'a';
+$o->key2 = 'b';
+
+$yaml->key1 = new Compact($o);
+
+return $yaml;
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/compact_object_in_array.php b/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/compact_object_in_array.php
new file mode 100644
index 0000000..5fb2a43
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/compact_object_in_array.php
@@ -0,0 +1,14 @@
+key = 'a';
+
+$yaml->key1 = new Compact([1,2,$o]);
+
+return $yaml;
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/dateTime.php b/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/dateTime.php
new file mode 100644
index 0000000..83c5f8b
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/dateTime.php
@@ -0,0 +1,9 @@
+key = new \DateTime('2000-01-01');
+
+return $yaml;
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/floats_double.php b/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/floats_double.php
new file mode 100644
index 0000000..a110626
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/floats_double.php
@@ -0,0 +1,22 @@
+positiveFloat = 0.5353;
+$yaml->negativeFloat = -2.65;
+
+$yaml->castedPositiveFloat = (float) 2.0;
+$yaml->castedNegativeFloat = (float) -2;
+
+$yaml->positiveExponentFloat = 2.3e4;
+$yaml->negativeExponentFloat = 2.3e-3;
+
+$yaml->positiveInfinity = INF;
+$yaml->negativeInfinity = -INF;
+
+$yaml->notANumber = NAN;//this has the PHP type 'double'
+
+
+return $yaml;
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/multidoc_yamlObject.php b/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/multidoc_yamlObject.php
new file mode 100644
index 0000000..ca2dd8b
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/multidoc_yamlObject.php
@@ -0,0 +1,15 @@
+a = 1;
+
+$yaml2->b = 2;
+
+return [$yaml, $yaml1, $yaml2];
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/netplan_example.php b/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/netplan_example.php
new file mode 100644
index 0000000..61612bd
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/netplan_example.php
@@ -0,0 +1,26 @@
+ new \stdClass, 'version' => 2];
+
+
+
+$enp0s3 = (object) [
+ 'addresses' => new Compact(['192.168.1.84/24']),
+ 'gateway4' => '192.168.1.1',
+ 'nameservers' => new \stdClass
+];
+
+
+$enp0s3->nameservers->addresses = new Compact(['192.168.1.1']);
+$network->ethernets->enp0s3 = $enp0s3;
+
+$yaml->network = $network;
+
+
+return $yaml;
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/stdObject.php b/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/stdObject.php
new file mode 100644
index 0000000..93e39a1
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/stdObject.php
@@ -0,0 +1,6 @@
+prop = "my property";
+
+return $yaml;
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/symfony_custom_tag.php b/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/symfony_custom_tag.php
new file mode 100644
index 0000000..0c31499
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/symfony_custom_tag.php
@@ -0,0 +1,10 @@
+foo = 'bar';
+
+
+return new Tagged('!php/object', $object);
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/unicodeString.php b/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/unicodeString.php
new file mode 100644
index 0000000..a769e1c
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/unicodeString.php
@@ -0,0 +1,6 @@
+str = "☺";
+
+return $yaml;
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/yamlObject_indices.php b/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/yamlObject_indices.php
new file mode 100644
index 0000000..bd39c94
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/yamlObject_indices.php
@@ -0,0 +1,14 @@
+memberOfO = 'some really really really really really really really really really very long text as a simple string';
+
+$yaml[0] = [1,2,3];
+
+$yaml[1] = $o;
+
+return $yaml;
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/yamlObject_properties.php b/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/yamlObject_properties.php
new file mode 100644
index 0000000..d75d44d
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/dumping/yamlObject_properties.php
@@ -0,0 +1,15 @@
+memberOfO = 'some really really really really really really really really really very long text as a simple string';
+
+$yaml->propA = [1,2,3];
+
+$yaml->propB = $o;
+
+return $yaml;
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_01.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_01.yml
new file mode 100644
index 0000000..bb35fad
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_01.yml
@@ -0,0 +1,6 @@
+#Example 2.1. Sequence of Scalars
+#(ball players)
+
+- Mark McGwire
+- Sammy Sosa
+- Ken Griffey
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_02.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_02.yml
new file mode 100644
index 0000000..1947836
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_02.yml
@@ -0,0 +1,6 @@
+#Example 2.2. Mapping Scalars to Scalars
+#(player statistics)
+
+hr: 65 # Home runs
+avg: 0.278 # Batting average
+rbi: 147 # Runs Batted In
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_03.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_03.yml
new file mode 100644
index 0000000..41798e1
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_03.yml
@@ -0,0 +1,11 @@
+#Example 2.3. Mapping Scalars to Sequences
+#(ball clubs in each league)
+
+american:
+ - Boston Red Sox
+ - Detroit Tigers
+ - New York Yankees
+national:
+ - New York Mets
+ - Chicago Cubs
+ - Atlanta Braves
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_04.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_04.yml
new file mode 100644
index 0000000..f7e383e
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_04.yml
@@ -0,0 +1,11 @@
+#Example 2.4. Sequence of Mappings
+#(players’ statistics)
+
+-
+ name: Mark McGwire
+ hr: 65
+ avg: 0.278
+-
+ name: Sammy Sosa
+ hr: 63
+ avg: 0.288
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_05.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_05.yml
new file mode 100644
index 0000000..1b4ae88
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_05.yml
@@ -0,0 +1,7 @@
+#Example 2.5. Sequence of Sequences
+
+- [name , hr, avg ]
+- [Mark McGwire, 65, 0.278]
+- [Sammy Sosa , 63, 0.288]
+
+
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_06.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_06.yml
new file mode 100644
index 0000000..4e503b3
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_06.yml
@@ -0,0 +1,7 @@
+#Example 2.6. Mapping of Mappings
+
+Mark McGwire: {hr: 65, avg: 0.278}
+Sammy Sosa: {
+ hr: 63,
+ avg: 0.288
+ }
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_07.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_07.yml
new file mode 100644
index 0000000..467352b
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_07.yml
@@ -0,0 +1,13 @@
+#Example 2.7. Two Documents in a Stream
+#(each with a leading comment)
+
+# Ranking of 1998 home runs
+---
+- Mark McGwire
+- Sammy Sosa
+- Ken Griffey
+
+# Team ranking
+---
+- Chicago Cubs
+- St Louis Cardinals
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_08.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_08.yml
new file mode 100644
index 0000000..c41f9e6
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_08.yml
@@ -0,0 +1,13 @@
+#Example 2.8. Play by Play Feed
+#from a Game
+
+---
+time: 20:03:20
+player: Sammy Sosa
+action: strike (miss)
+...
+---
+time: 20:03:47
+player: Sammy Sosa
+action: grand slam
+...
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_09.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_09.yml
new file mode 100644
index 0000000..80e4310
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_09.yml
@@ -0,0 +1,11 @@
+#Example 2.9. Single Document with
+#Two Comments
+
+---
+hr: # 1998 hr ranking
+ - Mark McGwire
+ - Sammy Sosa
+rbi:
+ # 1998 rbi ranking
+ - Sammy Sosa
+ - Ken Griffey
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_10.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_10.yml
new file mode 100644
index 0000000..774f648
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_10.yml
@@ -0,0 +1,11 @@
+#Example 2.10. Node for “Sammy Sosa”
+#appears twice in this document
+
+---
+hr:
+ - Mark McGwire
+ # Following node labeled SS
+ - &SS Sammy Sosa
+rbi:
+ - *SS # Subsequent occurrence
+ - Ken Griffey
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_11.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_11.yml
new file mode 100644
index 0000000..1521fd3
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_11.yml
@@ -0,0 +1,11 @@
+#Example 2.11. Mapping between Sequences
+
+? - Detroit Tigers
+ - Chicago cubs
+:
+ - 2001-07-23
+
+? [ New York Yankees,
+ Atlanta Braves ]
+: [ 2001-07-02, 2001-08-12,
+ 2001-08-14 ]
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_12.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_12.yml
new file mode 100644
index 0000000..81dfb7b
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_12.yml
@@ -0,0 +1,11 @@
+#Example 2.12. Compact Nested Mapping
+
+---
+# Products purchased
+- item : Super Hoop
+ quantity: 1
+- item : Basketball
+ quantity: 4
+- item : Big Shoes
+ quantity: 1
+
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_13.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_13.yml
new file mode 100644
index 0000000..f8ea951
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_13.yml
@@ -0,0 +1,7 @@
+#Example 2.13. In literals,
+#newlines are preserved
+
+# ASCII Art
+--- |
+ \//||\/||
+ // || ||__
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_14.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_14.yml
new file mode 100644
index 0000000..7667fa3
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_14.yml
@@ -0,0 +1,7 @@
+#Example 2.14. In the folded scalars,
+#newlines become spaces
+
+--- >
+ Mark McGwire's
+ year was crippled
+ by a knee injury.
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_15.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_15.yml
new file mode 100644
index 0000000..c983005
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_15.yml
@@ -0,0 +1,11 @@
+#Example 2.15. Folded newlines are preserved
+#for "more indented" and blank lines
+
+>
+ Sammy Sosa completed another
+ fine season with great stats.
+
+ 63 Home Runs
+ 0.288 Batting Average
+
+ What a year!
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_16.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_16.yml
new file mode 100644
index 0000000..432a4c2
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_16.yml
@@ -0,0 +1,11 @@
+#Example 2.16. Indentation determines scope
+
+
+name: Mark McGwire
+accomplishment: >
+ Mark set a major league
+ home run record in 1998.
+stats: |
+ 65 Home Runs
+ 0.278 Batting Average
+
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_17.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_17.yml
new file mode 100644
index 0000000..25c213f
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_17.yml
@@ -0,0 +1,9 @@
+#Example 2.17. Quoted Scalars
+
+unicode: "Sosa did fine.\u263A"
+control: "\b1998\t1999\t2000\n"
+hex esc: "\x0d\x0a is \r\n"
+
+single: '"Howdy!" he cried.'
+quoted: ' # Not a ''comment''.'
+tie-fighter: '|\-*-/|'
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_18.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_18.yml
new file mode 100644
index 0000000..ad9d05d
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_18.yml
@@ -0,0 +1,9 @@
+#Example 2.18. Multi-line Flow Scalars
+
+plain:
+ This unquoted scalar
+ spans many lines.
+
+quoted: "So does this
+ quoted scalar.\n"
+
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_19.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_19.yml
new file mode 100644
index 0000000..4860baa
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_19.yml
@@ -0,0 +1,7 @@
+#Example 2.19. Integers
+
+canonical: 12345
+decimal: +12345
+octal: 0o14
+hexadecimal: 0xC
+
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_20.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_20.yml
new file mode 100644
index 0000000..1d6fe33
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_20.yml
@@ -0,0 +1,7 @@
+#Example 2.20. Floating Point
+
+canonical: 1.23015e+3
+exponential: 12.3015e+02
+fixed: 1230.15
+negative infinity: -.inf
+not a number: .NaN
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_21.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_21.yml
new file mode 100644
index 0000000..beec7e5
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_21.yml
@@ -0,0 +1,5 @@
+#Example 2.21. Miscellaneous
+
+null:
+booleans: [ true, false ]
+string: '012345'
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_22.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_22.yml
new file mode 100644
index 0000000..c7472b0
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_22.yml
@@ -0,0 +1,6 @@
+#Example 2.22. Timestamps
+
+canonical: 2001-12-15T02:59:43.1Z
+iso8601: 2001-12-14t21:59:43.10-05:00
+spaced: 2001-12-14 21:59:43.10 -5
+date: 2002-12-14
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_23.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_23.yml
new file mode 100644
index 0000000..746101f
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_23.yml
@@ -0,0 +1,16 @@
+#Example 2.23. Various Explicit Tags
+
+---
+not-date: !!str 2002-04-28
+
+picture: !!binary |
+ R0lGODlhDAAMAIQAAP//9/X
+ 17unp5WZmZgAAAOfn515eXv
+ Pz7Y6OjuDg4J+fn5OTk6enp
+ 56enmleECcgggoBADs=
+
+application specific tag: !something |
+ The semantics of the tag
+ above may be different for
+ different documents.
+
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_24.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_24.yml
new file mode 100644
index 0000000..e403d55
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_24.yml
@@ -0,0 +1,16 @@
+#Example 2.24. Global Tags
+
+%TAG ! tag:clarkevans.com,2002:
+--- !shape
+ # Use the ! handle for presenting
+ # tag:clarkevans.com,2002:circle
+- !circle
+ center: &ORIGIN {x: 73, y: 129}
+ radius: 7
+- !line
+ start: *ORIGIN
+ finish: { x: 89, y: 102 }
+- !label
+ start: *ORIGIN
+ color: 0xFFEEBB
+ text: Pretty vector drawing.
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_25.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_25.yml
new file mode 100644
index 0000000..83c4202
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_25.yml
@@ -0,0 +1,9 @@
+#Example 2.25. Unordered Sets
+
+# Sets are represented as a
+# Mapping where each key is
+# associated with a null value
+--- !!set
+? Mark McGwire
+? Sammy Sosa
+? Ken Griffey
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_26.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_26.yml
new file mode 100644
index 0000000..2c5a1d8
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_26.yml
@@ -0,0 +1,9 @@
+#Example 2.26. Ordered Mappings
+
+# Ordered maps are represented as
+# A sequence of mappings, with
+# each mapping having one key
+--- !!omap
+- Mark McGwire: 65
+- Sammy Sosa: 63
+- Ken Griffy: 58
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_27.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_27.yml
new file mode 100644
index 0000000..4308998
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_27.yml
@@ -0,0 +1,31 @@
+#Example 2.27. Invoice
+
+--- !
+invoice: 34843
+date : 2001-01-23
+bill-to: &id001
+ given : Chris
+ family : Dumars
+ address:
+ lines: |
+ 458 Walkman Dr.
+ Suite #292
+ city : Royal Oak
+ state : MI
+ postal : 48046
+ship-to: *id001
+product:
+ - sku : BL394D
+ quantity : 4
+ description : Basketball
+ price : 450.00
+ - sku : BL4438H
+ quantity : 1
+ description : Super Hoop
+ price : 2392.00
+tax : 251.42
+total: 4443.52
+comments:
+ Late afternoon is best.
+ Backup contact is Nancy
+ Billsmer @ 338-4338.
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_28.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_28.yml
new file mode 100644
index 0000000..d953ce0
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/examples/Example_2_28.yml
@@ -0,0 +1,31 @@
+#Example 2.28. Log File
+
+---
+Time: 2001-11-23 15:01:42 -5
+User: ed
+Warning:
+ This is an error message
+ for the log file
+---
+Time: 2001-11-23 15:02:31 -5
+User: ed
+Warning:
+ A slightly different error
+ message.
+---
+Date: 2001-11-23 15:03:17 -5
+User: ed
+Fatal:
+ Unknown variable "bar"
+Stack:
+ - file: TopClass.py
+ line: 23
+ code: |
+ x = MoreObject("345\n")
+ - file: MoreClass.py
+ line: 58
+ code: |-
+ foo = bar
+
+
+
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/compact_keyvalue_with_no_space.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/compact_keyvalue_with_no_space.yml
new file mode 100644
index 0000000..5ab2218
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/compact_keyvalue_with_no_space.yml
@@ -0,0 +1 @@
+key: {foo:""}
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/compact_with_emptystring_as_key.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/compact_with_emptystring_as_key.yml
new file mode 100644
index 0000000..5bf45db
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/compact_with_emptystring_as_key.yml
@@ -0,0 +1 @@
+key: { "": foo }
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/compact_with_invalid_reference.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/compact_with_invalid_reference.yml
new file mode 100644
index 0000000..62976b1
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/compact_with_invalid_reference.yml
@@ -0,0 +1 @@
+key: { foo: * }
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/compact_with_invalid_reference_and_comment.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/compact_with_invalid_reference_and_comment.yml
new file mode 100644
index 0000000..efe2f3f
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/compact_with_invalid_reference_and_comment.yml
@@ -0,0 +1 @@
+key: { foo: * #foo }
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/compact_with_no_closing_brace.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/compact_with_no_closing_brace.yml
new file mode 100644
index 0000000..698dd62
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/compact_with_no_closing_brace.yml
@@ -0,0 +1 @@
+key: {abc: 'def'
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/compact_with_no_keyvalue_pair.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/compact_with_no_keyvalue_pair.yml
new file mode 100644
index 0000000..2c39e4f
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/compact_with_no_keyvalue_pair.yml
@@ -0,0 +1 @@
+key: {this, is not, supported}
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/invalid_quoting_in_compact.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/invalid_quoting_in_compact.yml
new file mode 100644
index 0000000..7b6fe78
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/invalid_quoting_in_compact.yml
@@ -0,0 +1 @@
+key: { "foo " bar": "bar" }
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/key_in_key.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/key_in_key.yml
new file mode 100644
index 0000000..a268264
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/key_in_key.yml
@@ -0,0 +1 @@
+foo: bar: baz
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/litteral_folded_with_immediate_value.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/litteral_folded_with_immediate_value.yml
new file mode 100644
index 0000000..f4f206a
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/litteral_folded_with_immediate_value.yml
@@ -0,0 +1 @@
+foo: > foo
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/litteral_with_immediate_value.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/litteral_with_immediate_value.yml
new file mode 100644
index 0000000..60ba9d0
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/litteral_with_immediate_value.yml
@@ -0,0 +1 @@
+foo: | foo
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/map_key_with_scalar_and_sequence.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/map_key_with_scalar_and_sequence.yml
new file mode 100644
index 0000000..461eef3
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/map_key_with_scalar_and_sequence.yml
@@ -0,0 +1,3 @@
+yaml:
+ hash: me
+ - array stuff
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/map_with_compact_with_duplicates.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/map_with_compact_with_duplicates.yml
new file mode 100644
index 0000000..929c1b0
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/map_with_compact_with_duplicates.yml
@@ -0,0 +1,2 @@
+parent: { child: first, child: duplicate }
+parent: { child: duplicate, child: duplicate }
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/map_with_embed_compact_and_comment.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/map_with_embed_compact_and_comment.yml
new file mode 100644
index 0000000..4fe73d5
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/map_with_embed_compact_and_comment.yml
@@ -0,0 +1,7 @@
+a:
+ b:
+ {}
+# comment
+ d:
+ 1.1
+# another comment
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/seq_items_with_multiline_scalar_and_key.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/seq_items_with_multiline_scalar_and_key.yml
new file mode 100644
index 0000000..94f7bd0
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/seq_items_with_multiline_scalar_and_key.yml
@@ -0,0 +1,4 @@
+foo:
+ - bar
+"missing colon"
+ foo: bar
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/seq_items_with_scalar_and_key.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/seq_items_with_scalar_and_key.yml
new file mode 100644
index 0000000..88f3766
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/seq_items_with_scalar_and_key.yml
@@ -0,0 +1,3 @@
+yaml:
+ - array stuff
+ hash: me
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/sequence_items_with_no_space.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/sequence_items_with_no_space.yml
new file mode 100644
index 0000000..b3c9281
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/sequence_items_with_no_space.yml
@@ -0,0 +1,4 @@
+collection:
+-item1
+-item2
+-item3
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/special_chars_with_no_sense.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/special_chars_with_no_sense.yml
new file mode 100644
index 0000000..560db8b
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/special_chars_with_no_sense.yml
@@ -0,0 +1 @@
+ & * ! | > ' " % @ ` #, { asd a;sdasd }-@^qw3
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/string_alone_with_indented_key.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/string_alone_with_indented_key.yml
new file mode 100644
index 0000000..a4d53ac
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/string_alone_with_indented_key.yml
@@ -0,0 +1,2 @@
+a
+ b:
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/unescaped_double_quotes.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/unescaped_double_quotes.yml
new file mode 100644
index 0000000..e1257e8
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/unescaped_double_quotes.yml
@@ -0,0 +1 @@
+key: "don"t do somthin" like that"
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/unescaped_simple_quotes.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/unescaped_simple_quotes.yml
new file mode 100644
index 0000000..664b328
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/failing/unescaped_simple_quotes.yml
@@ -0,0 +1 @@
+key: 'don't do somthin' like that'
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/blockChomping.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/blockChomping.yml
new file mode 100644
index 0000000..422dbae
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/blockChomping.yml
@@ -0,0 +1,7 @@
+foo: |-
+ one
+ two
+bar: |-
+ one
+ two
+
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/blockChompingWithInsideBlank.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/blockChompingWithInsideBlank.yml
new file mode 100644
index 0000000..088f39f
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/blockChompingWithInsideBlank.yml
@@ -0,0 +1,4 @@
+bar: |
+ one
+
+ two
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/blockChompingWithKeep.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/blockChompingWithKeep.yml
new file mode 100644
index 0000000..7ebace9
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/blockChompingWithKeep.yml
@@ -0,0 +1,6 @@
+foo: |+
+ one
+ two
+bar: |+
+ one
+ two
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/blockChompingWithKeepAndTrailing.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/blockChompingWithKeepAndTrailing.yml
new file mode 100644
index 0000000..9fb0c5d
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/blockChompingWithKeepAndTrailing.yml
@@ -0,0 +1,8 @@
+foo: |+
+ one
+ two
+
+bar: |+
+ one
+ two
+
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/blockChompingWithStripAndTrailing.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/blockChompingWithStripAndTrailing.yml
new file mode 100644
index 0000000..f1d1e3f
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/blockChompingWithStripAndTrailing.yml
@@ -0,0 +1,9 @@
+foo: |-
+ one
+ two
+
+bar: |-
+ one
+ two
+
+
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/comment_at_first_and_inside_map.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/comment_at_first_and_inside_map.yml
new file mode 100644
index 0000000..f26c9d7
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/comment_at_first_and_inside_map.yml
@@ -0,0 +1,10 @@
+# comment 1
+services:
+# comment 2
+ # comment 3
+ app.foo_service:
+ class: Foo
+# comment 4
+ # comment 5
+ app/bar_service:
+ class: Bar
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/comment_between_keys.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/comment_between_keys.yml
new file mode 100644
index 0000000..ccace20
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/comment_between_keys.yml
@@ -0,0 +1,6 @@
+a:
+ b: hello
+# c: |
+# first row
+# second row
+ d: hello
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/comment_inside_litteral_in_subkey.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/comment_inside_litteral_in_subkey.yml
new file mode 100644
index 0000000..84179de
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/comment_inside_litteral_in_subkey.yml
@@ -0,0 +1,13 @@
+test: |
+ foo
+ # bar
+ baz
+collection:
+ - one: |
+ foo
+ # bar
+ baz
+ - two: |
+ foo
+ # bar
+ baz
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/comment_with_a_colon.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/comment_with_a_colon.yml
new file mode 100644
index 0000000..4caa2bd
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/comment_with_a_colon.yml
@@ -0,0 +1,2 @@
+foo:
+ bar: foobar # Note: a comment after a colon
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/comments_inside_litteral.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/comments_inside_litteral.yml
new file mode 100644
index 0000000..ff39561
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/comments_inside_litteral.yml
@@ -0,0 +1,13 @@
+pages:
+ -
+ title: some title
+ content: |
+ # comment 1
+ header
+
+ # comment 2
+
+ title
+
+
+ footer # comment3
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/compact_array_inside_mapping.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/compact_array_inside_mapping.yml
new file mode 100644
index 0000000..a37b8b8
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/compact_array_inside_mapping.yml
@@ -0,0 +1,2 @@
+foo:
+ fiz: [cat]
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/compact_in_a_key_separated_by_comment.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/compact_in_a_key_separated_by_comment.yml
new file mode 100644
index 0000000..0d2e9e2
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/compact_in_a_key_separated_by_comment.yml
@@ -0,0 +1,4 @@
+foo:
+ - bar:
+ # comment
+ baz: [1, 2, 3]
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/complex_mapping.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/complex_mapping.yml
new file mode 100644
index 0000000..ea6ced6
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/complex_mapping.yml
@@ -0,0 +1,3 @@
+? "1"
+:
+ name: végétalien
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/complex_mapping_in_item.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/complex_mapping_in_item.yml
new file mode 100644
index 0000000..2575697
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/complex_mapping_in_item.yml
@@ -0,0 +1,3 @@
+- ? "1"
+ :
+ name: végétalien
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/complex_mapping_in_key.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/complex_mapping_in_key.yml
new file mode 100644
index 0000000..bd32209
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/complex_mapping_in_key.yml
@@ -0,0 +1,4 @@
+diet:
+ ? "1"
+ :
+ name: végétalien
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/directive_at_start.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/directive_at_start.yml
new file mode 100644
index 0000000..657d842
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/directive_at_start.yml
@@ -0,0 +1,4 @@
+%YAML 1.2
+---
+foo: 1
+bar: 2
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/encoding_non_utf8.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/encoding_non_utf8.yml
new file mode 100644
index 0000000..326321d
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/encoding_non_utf8.yml
@@ -0,0 +1,3 @@
+foo: 'äöüß'
+euro: '€'
+cp1252: '©ÉÇáñ'
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/endOfDocumentMapping.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/endOfDocumentMapping.yml
new file mode 100644
index 0000000..c591fd3
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/endOfDocumentMapping.yml
@@ -0,0 +1,3 @@
+--- %YAML:1.0
+foo: bar
+...
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/endOfDocumentSequence.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/endOfDocumentSequence.yml
new file mode 100644
index 0000000..83fdfd4
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/endOfDocumentSequence.yml
@@ -0,0 +1,5 @@
+--- %YAML:1.0
+- 1
+- 2
+- 3
+...
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/folded_with_alpha_keys.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/folded_with_alpha_keys.yml
new file mode 100644
index 0000000..7c38c52
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/folded_with_alpha_keys.yml
@@ -0,0 +1,5 @@
+folded_with_alpha_keys: |
+ - alphakey: alphakey_value
+ - 0: a
+ - 1: b
+ - 2: c
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/key_separated_by_blank_line.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/key_separated_by_blank_line.yml
new file mode 100644
index 0000000..c5dd712
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/key_separated_by_blank_line.yml
@@ -0,0 +1,3 @@
+foo:
+
+ bar: baz
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/keysWithTabs.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/keysWithTabs.yml
new file mode 100644
index 0000000..dfa0741
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/keysWithTabs.yml
@@ -0,0 +1,4 @@
+foo: bar
+baz: bar
+bor: baz
+boz: biz
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/literalFolded_clip.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/literalFolded_clip.yml
new file mode 100644
index 0000000..b807472
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/literalFolded_clip.yml
@@ -0,0 +1,7 @@
+foo: >
+ one
+ two
+bar: >
+ one
+ two
+
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/literalFolded_strip_trailing.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/literalFolded_strip_trailing.yml
new file mode 100644
index 0000000..6ad1808
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/literalFolded_strip_trailing.yml
@@ -0,0 +1,8 @@
+foo: >-
+ one
+ two
+
+bar: >-
+ one
+ two
+
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/literal_empty_with_blank.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/literal_empty_with_blank.yml
new file mode 100644
index 0000000..34fdd2a
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/literal_empty_with_blank.yml
@@ -0,0 +1,37 @@
+compact_keyvalue_with_no_space: |
+
+compact_with_invalid_reference_and_comment: |
+
+compact_with_invalid_reference: |
+
+compact_with_no_closing_brace: |
+
+compact_with_no_keyvalue_pair: |
+
+invalid_quoting_in_compact: |
+
+key_in_key: |
+
+litteral_folded_with_immediate_value: |
+
+litteral_with_immediate_value: |
+
+map_key_with_scalar_and_sequence: |
+
+map_with_compact_with_duplicates: |
+
+map_with_embed_compact_and_comment: |
+
+seq_items_with_multiline_scalar_and_key: |
+
+seq_items_with_scalar_and_key: |
+
+sequence_items_with_no_space: |
+
+special_chars_with_no_sense: |
+
+string_alone_with_indented_key: |
+
+unescaped_double_quotes: |
+
+unescaped_simple_quotes: |
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/literal_keep_trailing_leading.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/literal_keep_trailing_leading.yml
new file mode 100644
index 0000000..e7c74a3
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/literal_keep_trailing_leading.yml
@@ -0,0 +1,5 @@
+foo: |-
+
+
+ bar
+
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/literal_with_comment_at_lastline.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/literal_with_comment_at_lastline.yml
new file mode 100644
index 0000000..f047f13
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/literal_with_comment_at_lastline.yml
@@ -0,0 +1,10 @@
+content: |
+ # comment 1
+ header
+
+ # comment 2
+
+ title
+
+
+ footer # comment3
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/literalfolded_keep_trailing.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/literalfolded_keep_trailing.yml
new file mode 100644
index 0000000..b6d819f
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/literalfolded_keep_trailing.yml
@@ -0,0 +1,6 @@
+foo: >+
+ one
+ two
+bar: >+
+ one
+ two
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/map_and_items_mixing.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/map_and_items_mixing.yml
new file mode 100644
index 0000000..6846f97
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/map_and_items_mixing.yml
@@ -0,0 +1,11 @@
+sequence:
+- key: foo
+-
+ key1: foo
+- key2: foo
+ key3: foo
+sequence2:
+- keyempty:
+ subkeyempty:
+- keyempty2:
+ samelvlkey: foo
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/map_in_an_empty_item.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/map_in_an_empty_item.yml
new file mode 100644
index 0000000..17b2e38
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/map_in_an_empty_item.yml
@@ -0,0 +1,3 @@
+foo:
+ -
+ bar: foobar
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/map_with_boolean_keys.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/map_with_boolean_keys.yml
new file mode 100644
index 0000000..dd1ec83
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/map_with_boolean_keys.yml
@@ -0,0 +1,2 @@
+true: foo
+false: bar
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/map_with_comment_inside_item.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/map_with_comment_inside_item.yml
new file mode 100644
index 0000000..f1e12c3
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/map_with_comment_inside_item.yml
@@ -0,0 +1,4 @@
+foo:
+ - bar: "foobar"
+ # A comment
+ baz: "foobaz"
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/map_with_duplicate_keys.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/map_with_duplicate_keys.yml
new file mode 100644
index 0000000..eb5b5d7
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/map_with_duplicate_keys.yml
@@ -0,0 +1,6 @@
+parent:
+ child: first
+ child: duplicate
+parent:
+ child: duplicate
+ child: duplicate
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/map_with_float_keys.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/map_with_float_keys.yml
new file mode 100644
index 0000000..0fb53f6
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/map_with_float_keys.yml
@@ -0,0 +1,3 @@
+foo:
+ 1.2: "bar"
+ 1.3: "baz"
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/map_with_number_keys.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/map_with_number_keys.yml
new file mode 100644
index 0000000..291ffbb
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/map_with_number_keys.yml
@@ -0,0 +1,3 @@
+map:
+ 1: one
+ 2: two
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/map_with_quoted_number_keys.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/map_with_quoted_number_keys.yml
new file mode 100644
index 0000000..e9e0f43
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/map_with_quoted_number_keys.yml
@@ -0,0 +1,3 @@
+map:
+ '0': one
+ '1': two
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/map_with_seq_and_embed_comment.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/map_with_seq_and_embed_comment.yml
new file mode 100644
index 0000000..dcc0be0
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/map_with_seq_and_embed_comment.yml
@@ -0,0 +1,5 @@
+a:
+ b:
+ - c
+# comment
+ d: e
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/multidoc_empty_doc_in_between.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/multidoc_empty_doc_in_between.yml
new file mode 100644
index 0000000..dba8eae
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/multidoc_empty_doc_in_between.yml
@@ -0,0 +1,13 @@
+# Ranking of 1998 home runs
+---
+- Mark McGwire
+- Sammy Sosa
+- Ken Griffey
+...
+---
+# this document is empty
+...
+# Team ranking
+---
+- Chicago Cubs
+- St Louis Cardinals
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/multidoc_mapping.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/multidoc_mapping.yml
new file mode 100644
index 0000000..2160ffe
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/multidoc_mapping.yml
@@ -0,0 +1,10 @@
+# Ranking of 1998 home runs
+---
+a: Mark McGwire
+b: Sammy Sosa
+c: Ken Griffey
+
+---
+# Team ranking
+a: Chicago Cubs
+b: St Louis Cardinals
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/multidoc_sequence.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/multidoc_sequence.yml
new file mode 100644
index 0000000..b93cbf3
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/multidoc_sequence.yml
@@ -0,0 +1,10 @@
+# Ranking of 1998 home runs
+---
+- Mark McGwire
+- Sammy Sosa
+- Ken Griffey
+
+# Team ranking
+---
+- Chicago Cubs
+- St Louis Cardinals
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/multiline_indented.keyslookalike.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/multiline_indented.keyslookalike.yml
new file mode 100644
index 0000000..bae9562
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/multiline_indented.keyslookalike.yml
@@ -0,0 +1,3 @@
+a:
+ b
+ c
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/multiline_quoted.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/multiline_quoted.yml
new file mode 100644
index 0000000..c790d56
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/multiline_quoted.yml
@@ -0,0 +1,5 @@
+foo: "bar
+ baz
+ foobar
+foo"
+bar: baz
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/multiline_quoted_in_subkey.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/multiline_quoted_in_subkey.yml
new file mode 100644
index 0000000..4e67582
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/multiline_quoted_in_subkey.yml
@@ -0,0 +1,4 @@
+foo:
+ foobar: 'foo
+ #bar'
+ bar: baz
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/multiline_quoted_with_blank.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/multiline_quoted_with_blank.yml
new file mode 100644
index 0000000..673f70f
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/multiline_quoted_with_blank.yml
@@ -0,0 +1,3 @@
+foobar: 'foo
+
+ bar'
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/multiline_quoted_with_slash.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/multiline_quoted_with_slash.yml
new file mode 100644
index 0000000..0d2ba32
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/multiline_quoted_with_slash.yml
@@ -0,0 +1,3 @@
+foobar:
+ "foo\
+ bar"
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/multiline_unquoted.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/multiline_unquoted.yml
new file mode 100644
index 0000000..7893ddf
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/multiline_unquoted.yml
@@ -0,0 +1,5 @@
+foo: bar
+ baz
+ foobar
+ foo
+bar: baz
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/multiline_unquoted_in_seqitem.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/multiline_unquoted_in_seqitem.yml
new file mode 100644
index 0000000..e95ed77
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/multiline_unquoted_in_seqitem.yml
@@ -0,0 +1,6 @@
+foo:
+- bar:
+ one
+
+ two
+ three
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/multiline_with_boolean_strings.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/multiline_with_boolean_strings.yml
new file mode 100644
index 0000000..34701e5
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/multiline_with_boolean_strings.yml
@@ -0,0 +1,4 @@
+test:
+ You can have things that don't look like strings here
+ true
+ yes you can
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/references_inside_compact.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/references_inside_compact.yml
new file mode 100644
index 0000000..30f66ff
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/references_inside_compact.yml
@@ -0,0 +1,9 @@
+var: &var var-value
+scalar: *var
+list: [ *var ]
+list_in_list: [[ *var ]]
+map_in_list: [ { key: *var } ]
+embedded_mapping: { key: *var }
+map: { key: *var }
+list_in_map: { key: [*var] }
+map_in_map: { foo: { bar: *var } }
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/references_inside_merged_key.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/references_inside_merged_key.yml
new file mode 100644
index 0000000..5d93d03
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/references_inside_merged_key.yml
@@ -0,0 +1,8 @@
+mergekeyrefdef:
+ a: foo
+ <<: &quux
+ b: bar
+ c: baz
+mergekeyderef:
+ d: quux
+ <<: *quux
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/references_with_merge.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/references_with_merge.yml
new file mode 100644
index 0000000..a72cf0e
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/references_with_merge.yml
@@ -0,0 +1,4 @@
+foo: &foo
+ baz: foobar
+bar:
+ <<: *foo
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/references_with_merged_keys_inside_compact.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/references_with_merged_keys_inside_compact.yml
new file mode 100644
index 0000000..79e715f
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/references_with_merged_keys_inside_compact.yml
@@ -0,0 +1,12 @@
+foo: &FOO
+ bar: 1
+bar: &BAR
+ baz: 2
+ <<: *FOO
+baz:
+ baz_foo: 3
+ <<:
+ baz_bar: 4
+foobar:
+ bar: ~
+ <<: [*FOO, *BAR]
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/references_with_selfreference.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/references_with_selfreference.yml
new file mode 100644
index 0000000..05566c1
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/references_with_selfreference.yml
@@ -0,0 +1 @@
+foo: { &foo { a: Steve, <<: *foo} }
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/sequence_item_with_multiple_keys.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/sequence_item_with_multiple_keys.yml
new file mode 100644
index 0000000..2f61625
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/sequence_item_with_multiple_keys.yml
@@ -0,0 +1,3 @@
+collection:
+- key: foo
+ foo: bar
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/sequence_with_duplicate_keys.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/sequence_with_duplicate_keys.yml
new file mode 100644
index 0000000..0701e16
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/sequence_with_duplicate_keys.yml
@@ -0,0 +1,3 @@
+array:
+ - key: one
+ - key: two
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/sequence_with_linefeed_and_key.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/sequence_with_linefeed_and_key.yml
new file mode 100644
index 0000000..cee00fd
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/sequence_with_linefeed_and_key.yml
@@ -0,0 +1,7 @@
+a:
+-
+ b:
+ -
+ bar: baz
+- foo
+d: e
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/tag_symfony_phpobject.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/tag_symfony_phpobject.yml
new file mode 100644
index 0000000..ecbaa28
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/tag_symfony_phpobject.yml
@@ -0,0 +1,2 @@
+foo: !php/object O:30:"Symfony\Component\Yaml\Tests\B":1:{s:1:"b";s:3:"foo";}
+bar: 1
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/tags_as_casting.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/tags_as_casting.yml
new file mode 100644
index 0000000..d83ee46
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/tags_as_casting.yml
@@ -0,0 +1,8 @@
+'1.2': "bar"
+!!str 1.3: "baz"
+
+'true': foo
+!!str false: bar
+
+!!str null: null
+'~': 'null'
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/tags_as_keys.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/tags_as_keys.yml
new file mode 100644
index 0000000..1cf19e0
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/tags_as_keys.yml
@@ -0,0 +1,5 @@
+transitions:
+ !php/const 'Symfony\Component\Yaml\Tests\B::FOO':
+ from:
+ - !php/const 'Symfony\Component\Yaml\Tests\B::BAR'
+ to: !php/const 'Symfony\Component\Yaml\Tests\B::BAZ'
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/tags_in_compact.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/tags_in_compact.yml
new file mode 100644
index 0000000..c964730
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/tags_in_compact.yml
@@ -0,0 +1,2 @@
+- !foo [foo, bar]
+- !quz {foo: bar, quz: !bar {one: bar}}
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/tags_in_item_with_sequence.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/tags_in_item_with_sequence.yml
new file mode 100644
index 0000000..585ffa2
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/tags_in_item_with_sequence.yml
@@ -0,0 +1,3 @@
+- !foo
+ - yaml
+- !quz [bar]
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/tags_in_litteral.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/tags_in_litteral.yml
new file mode 100644
index 0000000..8ead8cf
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/tags_in_litteral.yml
@@ -0,0 +1,2 @@
+data: !!binary |
+ SGVsbG8gd29ybGQ=
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/tags_in_mapping.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/tags_in_mapping.yml
new file mode 100644
index 0000000..0c12426
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/tags_in_mapping.yml
@@ -0,0 +1,4 @@
+!foo
+foo: !quz [bar]
+quz: !foo
+ quz: bar
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/tags_inline_long.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/tags_inline_long.yml
new file mode 100644
index 0000000..ff149c7
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/tags_inline_long.yml
@@ -0,0 +1,4 @@
+foo: !inline bar
+quz: !long >
+ this is a long
+ text
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/tags_with_quotes.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/tags_with_quotes.yml
new file mode 100644
index 0000000..c1d5a9c
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/tags_with_quotes.yml
@@ -0,0 +1,3 @@
+data: !!binary "SGVsbG8gd29ybGQ="
+'enclosed with single quotes' : !!binary 'SGVsbG8gd29ybGQ='
+'containing spaces' : !!binary "SGVs bG8gd 29ybGQ="
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/yaml_in_literal_folded.yml b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/yaml_in_literal_folded.yml
new file mode 100644
index 0000000..ee04a3a
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/cases/parsing/yaml_in_literal_folded.yml
@@ -0,0 +1,7 @@
+yamlObject_indices: |
+ -
+ - 1
+ - 2
+ - 3
+ -
+ memberOfO: some really really really really really really really really really very long text as a simple string
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/definitions/dumping_tests.yml b/classes/vendor/81x/dallgoot/yaml/tests/definitions/dumping_tests.yml
new file mode 100644
index 0000000..20eb0fb
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/definitions/dumping_tests.yml
@@ -0,0 +1,68 @@
+array: |-
+ - a
+ - b
+ - c
+array_with_alphakey: |-
+ alphakey: alphakey_value
+ 0: a
+ 1: b
+ 2: c
+compact_array_in_object: |-
+ key1: {array: [1, 2, 3]}
+compact_array: |-
+ key1: [1, 2, 3]
+compact_object_in_array: |-
+ key1: [1, 2, {key: a}]
+compact_object: |-
+ key1: {key1: a, key2: b}
+dateTime: |-
+ key: 2000-01-01
+floats_double: |-
+ positiveFloat: 0.5353
+ negativeFloat: -2.6500
+ castedPositiveFloat: 2.0000
+ castedNegativeFloat: -2.0000
+ positiveExponentFloat: 23000.0000
+ negativeExponentFloat: 0.0023
+ positiveInfinity: .inf
+ negativeInfinity: -.inf
+ notANumber: .nan
+stdObject: |-
+ prop: my property
+# symfony_custom_tag: |-
+# !php/object 'O:8:"stdClass":1:{s:5:"foo";s:7:"bar";}'
+yamlObject_indices: |-
+ -
+ - 1
+ - 2
+ - 3
+ -
+ memberOfO: some really really really really really really really really really very long text as a simple string
+yamlObject_properties: |-
+ propA:
+ - 1
+ - 2
+ - 3
+ propB:
+ memberOfO: some really really really really really really really really really very long text as a simple string
+unicodeString: |-
+ str: \u263a
+multidoc_yamlObject: |-
+ ---
+ -
+ - 1
+ - 2
+ - 3
+ ---
+ a: 1
+ ---
+ b: 2
+netplan_example: |-
+ network:
+ ethernets:
+ enp0s3:
+ addresses: [192.168.1.84\/24]
+ gateway4: 192.168.1.1
+ nameservers:
+ addresses: [192.168.1.1]
+ version: 2
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/definitions/examples_tests.yml b/classes/vendor/81x/dallgoot/yaml/tests/definitions/examples_tests.yml
new file mode 100644
index 0000000..0b19b62
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/definitions/examples_tests.yml
@@ -0,0 +1,56 @@
+Example_2_01: |-
+ ["Mark McGwire","Sammy Sosa","Ken Griffey"]
+Example_2_02: |-
+ {"hr":65,"avg":0.278,"rbi":147}
+Example_2_03: |-
+ {"american":["Boston Red Sox","Detroit Tigers","New York Yankees"],"national":["New York Mets","Chicago Cubs","Atlanta Braves"]}
+Example_2_04: |-
+ [{"name":"Mark McGwire","hr":65,"avg":0.278},{"name":"Sammy Sosa","hr":63,"avg":0.288}]
+Example_2_05: |-
+ [["name","hr","avg"],["Mark McGwire",65,0.278],["Sammy Sosa",63,0.288]]
+Example_2_06: |-
+ {"Mark McGwire":{"hr":65,"avg":0.278},"Sammy Sosa":{"hr":63,"avg":0.288}}
+Example_2_07: |-
+ [["Mark McGwire","Sammy Sosa","Ken Griffey"],["Chicago Cubs","St Louis Cardinals"]]
+Example_2_08: |-
+ [{"time":"20:03:20","player":"Sammy Sosa","action":"strike (miss)"},{"time":"20:03:47","player":"Sammy Sosa","action":"grand slam"}]
+Example_2_09: |-
+ {"hr":["Mark McGwire","Sammy Sosa"],"rbi":["Sammy Sosa","Ken Griffey"]}
+Example_2_10: |-
+ {"hr":["Mark McGwire","Sammy Sosa"],"rbi":["Sammy Sosa","Ken Griffey"]}
+Example_2_11: |-
+ {"[\"Detroit Tigers\"]":["2001-07-23"],"[\"New York Yankees\",\"Atlanta Braves\"]":["2001-07-02","2001-08-12","2001-08-14"]}
+Example_2_12: |-
+ [{"item":"Super Hoop","quantity":1},{"item":"Basketball","quantity":4},{"item":"Big Shoes","quantity":1}]
+Example_2_13: |-
+ ""\\//||\\/||\n// || ||__\n""
+Example_2_14: |-
+ ""Mark McGwire's year was crippled by a knee injury.\n""
+Example_2_15: |-
+ ""Sammy Sosa completed another fine season with great stats.\n\n63 Home Runs\n0.288 Batting Average\nWhat a year!\n""
+Example_2_16: |-
+ {"name":"Mark McGwire","accomplishment":"Mark set a major league home run record in 1998.\n","stats":"65 Home Runs\n0.278 Batting Average\n"}
+Example_2_17: |-
+ "{"unicode":"Sosa did fine.\u263A","control":"\\b1998\\t1999\\t2000\\n","hex esc":"\x0d\x0a is \\r\\n","single":"\"Howdy!\" he cried.","quoted":" # Not a ''comment''.","tie-fighter":"|\\-*-/|"}"
+Example_2_18: |-
+ {"plain":"This unquoted scalar spans many lines.","quoted":"So does this quoted scalar.\\n"}
+Example_2_19: |-
+ {"canonical":12345,"decimal":12345,"octal":12,"hexadecimal":12}
+Example_2_20: |-
+ {"canonical":1230.15,"exponential":1230.15,"fixed":1230.15,"negative infinity":0,"not a number":0}
+Example_2_21: |-
+ {"null":null,"booleans":[true,false],"string":"012345"}
+Example_2_22: |-
+ {"canonical":"2001-12-15T02:59:43.1Z","iso8601":"2001-12-14t21:59:43.10-05:00","spaced":"2001-12-14 21:59:43.10 -5","date":"2002-12-14"}
+Example_2_23: |-
+ {"not-date":"2002-04-28","picture":"R0lGODlhDAAMAIQAAP//9/X17unp5WZmZgAAAOfn515eXvPz7Y6OjuDg4J+fn5OTk6enp56enmleECcgggoBADs=","application specific tag":{"tagName":"!something","value":"The semantics of the tag\nabove may be different for\ndifferent documents.\n"}}
+Example_2_24: |-
+ [{"tagName":"!circle","value":{"center":{"x":73,"y":129},"radius":7}},{"tagName":"!line","value":{"start":{"x":73,"y":129},"finish":{"x":89,"y":102}}},{"tagName":"!label","value":{"start":{"x":73,"y":129},"color":16772795,"text":"Pretty vector drawing."}}]
+Example_2_25: |-
+ {"Mark McGwire":null,"Sammy Sosa":null,"Ken Griffey":null}
+Example_2_26: |-
+ {"Mark McGwire":65,"Sammy Sosa":63,"Ken Griffy":58}
+Example_2_27: |-
+ "{"invoice":34843,"date":"2001-01-23","bill-to":{"given":"Chris","family":"Dumars","address":{"lines":"458 Walkman Dr.\nSuite\n","city":"Royal Oak","state":"MI","postal":48046}},"ship-to":{"given":"Chris","family":"Dumars","address":{"lines":"458 Walkman Dr.\nSuite\n","city":"Royal Oak","state":"MI","postal":48046}},"product":[{"sku":"BL394D","quantity":4,"description":"Basketball","price":450.0},{"sku":"BL4438H","quantity":1,"description":"Super Hoop","price":2392.0}],"tax":251.42,"total":4443.52,"comments":"Late afternoon is best. Backup contact is Nancy Billsmer @ 338-4338."}"
+Example_2_28: |-
+ [{"Time":"2001-11-23 15:01:42 -5","User":"ed","Warning":"This is an error message for the log file"},{"Time":"2001-11-23 15:02:31 -5","User":"ed","Warning":"A slightly different error message."},{"Date":"2001-11-23 15:03:17 -5","User":"ed","Fatal":"Unknown variable \"bar\"","Stack":[{"file":"TopClass.py","line":23,"code":"x = MoreObject(\"345\\n\")\n"},{"file":"MoreClass.py","line":58,"code":"foo = bar"}]}]
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/definitions/failing_tests.yml b/classes/vendor/81x/dallgoot/yaml/tests/definitions/failing_tests.yml
new file mode 100644
index 0000000..67b195e
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/definitions/failing_tests.yml
@@ -0,0 +1,21 @@
+compact_with_emptystring_as_key: |
+ {"key":{"":"foo"}}
+compact_keyvalue_with_no_space:
+compact_with_invalid_reference_and_comment:
+compact_with_invalid_reference:
+compact_with_no_closing_brace:
+compact_with_no_keyvalue_pair:
+invalid_quoting_in_compact:
+key_in_key:
+litteral_folded_with_immediate_value:
+litteral_with_immediate_value:
+map_key_with_scalar_and_sequence:
+map_with_compact_with_duplicates:
+map_with_embed_compact_and_comment:
+seq_items_with_multiline_scalar_and_key:
+seq_items_with_scalar_and_key:
+sequence_items_with_no_space:
+special_chars_with_no_sense:
+string_alone_with_indented_key:
+unescaped_double_quotes:
+unescaped_simple_quotes:
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/definitions/parsing_tests.yml b/classes/vendor/81x/dallgoot/yaml/tests/definitions/parsing_tests.yml
new file mode 100644
index 0000000..03137d1
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/definitions/parsing_tests.yml
@@ -0,0 +1,116 @@
+blockChompingWithInsideBlank: |-
+ {"bar":"one\n\ntwo\n"}
+blockChompingWithKeepAndTrailing: |-
+ {"foo":"one\ntwo\n\n","bar":"one\ntwo\n\n\n"}
+blockChompingWithKeep: |-
+ {"foo":"one\ntwo\n","bar":"one\ntwo\n"}
+blockChompingWithStripAndTrailing: |-
+ {"foo":"one\ntwo","bar":"one\ntwo"}
+blockChomping: |-
+ {"foo":"one\ntwo","bar":"one\ntwo"}
+comment_at_first_and_inside_map: |-
+ {"services":{"app.foo_service":{"class":"Foo"},"app/bar_service":{"class":"Bar"}}}
+comment_between_keys: |-
+ {"a":{"b":"hello","d":"hello"}}
+comment_inside_litteral_in_subkey: |-
+ {"test":"foo\nbaz\n","collection":[{"one":"foo\nbaz\n"},{"two":"foo\nbaz\n"}]}
+comments_inside_litteral: |-
+ {"pages":[{"title":"some title","content":"header\n\n \n title
\n \n\nfooter\n"}]}
+comment_with_a_colon: |-
+ {"foo":{"bar":"foobar"}}
+compact_array_inside_mapping: |-
+ {"foo":{"fiz":["cat"]}}
+compact_in_a_key_separated_by_comment: |-
+ {"foo":[{"bar":{"baz":[1,2,3]}}]}
+complex_mapping_in_item: |-
+ [{"1":{"name":"végétalien"}}]
+complex_mapping_in_key: |-
+ {"diet":{"1":{"name":"végétalien"}}}
+complex_mapping: |-
+ {"1":{"name":"végétalien"}}
+directive_at_start: |-
+ {"foo":1,"bar":2}
+encoding_non_utf8: |-
+ {"foo":"äöüß","euro":"€","cp1252":"©ÉÇáñ"}
+endOfDocumentMapping: |-
+ {"foo":"bar"}
+endOfDocumentSequence: |-
+ [1,2,3]
+folded_with_alpha_keys: |-
+ {"folded_with_alpha_keys":"- alphakey: alphakey_value\n- 0: a\n- 1: b\n- 2: c\n"}
+key_separated_by_blank_line: |-
+ {"foo":{"bar":"baz"}}
+keysWithTabs: |-
+ {"foo":"bar","baz":"bar","bor":"baz","boz":"biz"}
+literalfolded_keep_trailing: |-
+ {"foo":"one two\n","bar":"one two\n\n"}
+literalFolded_strip_trailing: |-
+ {"foo":"one two","bar":"one two"}
+literalFolded_clip: |-
+ {"foo":"one two\n","bar":"one two\n"}
+literal_keep_trailing_leading: |-
+ {"foo":"\n\nbar"}
+literal_with_comment_at_lastline: |-
+ {"content":"header\n\n \n title
\n \n\nfooter\n"}
+map_in_an_empty_item: |-
+ {"foo":[{"bar":"foobar"}]}
+map_with_boolean_keys: |-
+ {"true":"foo","false":"bar"}
+map_with_comment_inside_item: |-
+ {"foo":[{"bar":"foobar","baz":"foobaz"}]}
+map_with_duplicate_keys: |-
+ {"parent":{"child":"duplicate"}}
+map_with_float_keys: |-
+ {"foo":{"1.2":"bar","1.3":"baz"}}
+map_with_number_keys: |-
+ {"map":{"1":"one","2":"two"}}
+map_with_quoted_number_keys: |-
+ {"map":{"0":"one","1":"two"}}
+map_with_seq_and_embed_comment: |-
+ {"a":{"b":["c"],"d":"e"}}
+multidoc_mapping: |-
+ [{"a":"Mark McGwire","b":"Sammy Sosa","c":"Ken Griffey"},{"a":"Chicago Cubs","b":"St Louis Cardinals"}]
+multidoc_sequence: |-
+ [["Mark McGwire","Sammy Sosa","Ken Griffey"],["Chicago Cubs","St Louis Cardinals"]]
+multiline_indented.keyslookalike: |-
+ {"a":"b c"}
+multiline_quoted_in_subkey: |-
+ "{"foo":{"foobar":"foo #bar","bar":"baz"}}"
+multiline_quoted_with_blank: |-
+ {"foobar":"foo\nbar"}
+multiline_quoted_with_slash: |-
+ {"foobar":"foo\\ bar"}
+multiline_quoted: |-
+ {"foo":"bar baz foobar foo","bar":"baz"}
+multiline_unquoted_in_seqitem: |-
+ {"foo":[{"bar":"one\ntwo three"}]}
+multiline_unquoted: |-
+ {"foo":"bar baz foobar foo","bar":"baz"}
+multiline_with_boolean_strings: |-
+ {"test":"You can have things that don't look like strings here true yes you can"}
+references_inside_compact: |-
+ {"var":"var-value","scalar":"var-value","list":["var-value"],"list_in_list":[["var-value"]],"map_in_list":[{"key":"var-value"}],"embedded_mapping":{"key":"var-value"},"map":{"key":"var-value"},"list_in_map":{"key":["var-value"]},"map_in_map":{"foo":{"bar":"var-value"}}}
+sequence_item_with_multiple_keys: |-
+ {"collection":[{"key":"foo","foo":"bar"}]}
+sequence_with_duplicate_keys: |-
+ {"array":[{"key":"one"},{"key":"two"}]}
+sequence_with_linefeed_and_key: |-
+ {"a":[{"b":[{"bar":"baz"}]},"foo"],"d":"e"}
+tags_as_casting: |-
+ {"1.2":"bar","1.3":"baz","true":"foo","false":"bar","null":null,"~":"null"}
+tags_in_compact: |-
+ [{"tagName":"!foo","value":["foo","bar"]},{"tagName":"!quz","value":{"foo":"bar","quz":{"tagName":"!bar","value":{"one":"bar"}}}}]
+tags_in_item_with_sequence: |-
+ [{"tagName":"!foo","value":["yaml"]},{"tagName":"!quz","value":["bar"]}]
+tags_inline_long: |-
+ {"foo":{"tagName":"!inline","value":"bar"},"quz":{"tagName":"!long","value":"this is a long text\n"}}
+tags_in_litteral: |-
+ {"data":"SGVsbG8gd29ybGQ="}
+tags_in_mapping: |-
+ {"foo":{"tagName":"!quz","value":["bar"]},"quz":{"tagName":"!foo","value":{"quz":"bar"}}}
+tags_with_quotes: |-
+ {"data":"SGVsbG8gd29ybGQ=","enclosed with single quotes":"SGVsbG8gd29ybGQ=","containing spaces":"SGVs bG8gd 29ybGQ="}
+tag_symfony_phpobject: |-
+ {"foo":{"tagName":"!php/object","value":"O:30:\"Symfony\\Component\\Yaml\\Tests\\B\":1:{s:1:\"b\";s:3:\"foo\";}"},"bar":1}
+yaml_in_literal_folded: |-
+ {"yamlObject_indices":"-\n - 1\n - 2\n - 3\n-\n memberOfO: some really really really really really really really really really very long text as a simple string\n"}
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/units/BuilderTest.php b/classes/vendor/81x/dallgoot/yaml/tests/units/BuilderTest.php
new file mode 100644
index 0000000..5a0498c
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/units/BuilderTest.php
@@ -0,0 +1,236 @@
+.
+ * @license https://opensource.org/licenses/MIT The MIT license.
+ * @link https://github.com/dallgoot/yaml
+ * @since File available since Release 1.0.0
+ *
+ * @covers \Dallgoot\Yaml\Builder
+ */
+class BuilderTest extends TestCase
+{
+ /**
+ * @var Builder $builder An instance of "Builder" to test.
+ */
+ private $builder;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp(): void
+ {
+ /** @todo Maybe add some arguments to this constructor */
+ $this->builder = new Builder(0, 0);
+ }
+
+ private function buildSimpleMapping()
+ {
+ // create a yaml mapping
+ $root = new Root;
+ $root->add(new Key('key: value', 1));
+ return $this->builder->buildContent($root);
+ }
+
+ private function buildSimpleSequence()
+ {
+ // create a yaml sequence
+ $root = new Root;
+ $root->add(new Item('- itemvalue', 1));
+ return $this->builder->buildContent($root);
+ }
+
+ private function buildMultiDoc()
+ {
+ $root = new Root;
+ $root->add(new DocStart('---', 1));
+ $root->add(new Key('key: value', 2));
+ $root->add(new DocEnd('...', 3));
+ $root->add(new DocStart('---', 4));
+ $root->add(new Key('key: value', 5));
+ $root->add(new DocStart('---', 6));
+ $root->add(new Key('key: value', 7));
+ return $this->builder->buildContent($root);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Builder::buildContent
+ * @todo test :
+ * simple literal
+ * only JSON content
+ */
+ public function testBuildContent(): void
+ {
+ $debug_property = new \ReflectionProperty($this->builder, '_debug');
+ $debug_property->setAccessible(true);
+ $debug_property->setValue($this->builder, 2);
+ ob_start();
+ $this->assertEquals($this->builder->buildContent(new Root), null);
+ ob_end_clean();
+ }
+ /**
+ * @covers \Dallgoot\Yaml\Builder::buildContent
+ */
+ public function testBuildContentMAPPING(): void
+ {
+ //test simple mapping
+ $yamlMapping = $this->buildSimpleMapping();
+ $this->assertTrue($yamlMapping instanceof YamlObject);
+ $this->assertTrue(property_exists($yamlMapping, 'key'));
+ $this->assertEquals($yamlMapping->key, 'value');
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Builder::buildContent
+ */
+ public function testBuildContentSEQUENCE(): void
+ { //test simple sequence
+ $yamlSequence = $this->buildSimpleSequence();
+ $this->assertTrue($yamlSequence instanceof YamlObject);
+ $this->assertArrayHasKey(0, $yamlSequence);
+ $this->assertEquals($yamlSequence[0], 'itemvalue');
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Builder::buildContent
+ */
+ public function testBuildContentMULTIDOC(): void
+ {
+ // test multi document
+ $multiDoc = $this->buildMultiDoc();
+ $this->assertTrue(is_array($multiDoc));
+ $this->assertTrue(count($multiDoc) === 3);
+ $this->assertArrayHasKey(0, $multiDoc);
+ $this->assertTrue($multiDoc[0] instanceof YamlObject);
+ $this->assertArrayHasKey(1, $multiDoc);
+ $this->assertTrue($multiDoc[1] instanceof YamlObject);
+ $this->assertArrayHasKey(2, $multiDoc);
+ $this->assertTrue($multiDoc[2] instanceof YamlObject);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Builder::buildContent
+ * @todo test :
+ * simple literal
+ * only JSON content
+ * multidocument
+ */
+ // public function testBuildContentException(): void
+ // {
+ // $this->expectException(\Exception::class);
+ // $root = new Root;
+ // $root->value = null;
+ // $this->builder->buildContent($root);
+ // }
+
+ /**
+ * @covers \Dallgoot\Yaml\Builder::buildDocument
+ */
+ public function testBuildDocument(): void
+ {
+ $nodekey = new Key('key: keyvalue', 1);
+ $list = new NodeList($nodekey);
+ $yamlObject = $this->builder->buildDocument($list, 0);
+ $this->assertTrue($yamlObject instanceof YamlObject);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Builder::buildDocument
+ */
+ public function testBuildDocumentDebug(): void
+ {
+ $output =
+ "Document #0\n" .
+ "Dallgoot\Yaml\Nodes\Root Object\n" .
+ "(\n" .
+ " [line->indent] => 0 -> -1\n" .
+ " [value] => Dallgoot\Yaml\NodeList Object\n" .
+ " (\n" .
+ " [type] => \n" .
+ " [flags:SplDoublyLinkedList:private] => 0\n" .
+ " [dllist:SplDoublyLinkedList:private] => Array\n" .
+ " (\n" .
+ " )\n" .
+ "\n" .
+ " )\n" .
+ "\n" .
+ " [raw] => \n" .
+ " [parent] => NO PARENT!!!\n" .
+ ")\n";
+ $debug = new \ReflectionProperty(Builder::class, '_debug');
+ $debug->setAccessible(true);
+ $debug->setValue($this->builder, 3);
+ $list = new NodeList;
+ $this->builder->buildDocument($list, 0);
+ $this->expectOutputString($output);
+ $debug->setValue($this->builder, 0);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Builder::buildDocument
+ */
+ public function testBuildDocumentException(): void
+ {
+ $this->expectException(\Error::class);
+ $list = new NodeList();
+ $list->push(new \stdClass);
+ $yamlObject = $this->builder->buildDocument($list, 0);
+ }
+
+
+ /**
+ * @covers \Dallgoot\Yaml\Builder::pushAndSave
+ */
+ public function testPushAndSave(): void
+ {
+ $reflector = new \ReflectionClass($this->builder);
+ $method = $reflector->getMethod('pushAndSave');
+ $method->setAccessible(true);
+ $child = new DocEnd('', 1);
+ $buffer = new NodeList;
+ $documents = [];
+ $this->assertTrue($buffer->count() === 0);
+ $method->invokeArgs($this->builder, [$child, &$buffer, &$documents]);
+ $this->assertTrue($buffer->count() === 0);
+ $this->assertTrue(count($documents) === 1);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Builder::saveAndPush
+ */
+ public function testSaveAndPush(): void
+ {
+ $reflector = new \ReflectionClass($this->builder);
+ $method = $reflector->getMethod('saveAndPush');
+ $method->setAccessible(true);
+ $itemNode = new Item('- item', 1);
+ $buffer = new NodeList($itemNode);
+ $child = new DocStart('', 1);
+ $documents = [];
+ $this->assertTrue($buffer->count() === 1);
+ $method->invokeArgs($this->builder, [$child, &$buffer, &$documents]);
+ $this->assertTrue($buffer->count() === 1);
+ $this->assertTrue(count($documents) === 1);
+ $this->assertTrue($buffer->offsetGet(0) instanceof DocStart);
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/units/DumperHandlersTest.php b/classes/vendor/81x/dallgoot/yaml/tests/units/DumperHandlersTest.php
new file mode 100644
index 0000000..5ac7728
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/units/DumperHandlersTest.php
@@ -0,0 +1,143 @@
+.
+ * @license https://opensource.org/licenses/MIT The MIT license.
+ * @link https://github.com/dallgoot/yaml
+ * @since File available since Release 1.0.0
+ *
+ * @covers \Dallgoot\Yaml\DumperHandlers
+ */
+class DumperHandlersTest extends TestCase
+{
+ /**
+ * @var DumperHandlers $dumperHandler An instance of "DumperHandlers" to test.
+ */
+ public $dumperHandler;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp(): void
+ {
+ /** @todo Maybe add some arguments to this constructor */
+ $this->dumperHandler = new DumperHandlers(new Dumper);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\DumperHandlers::__construct
+ */
+ public function test__construct()
+ {
+ $this->dumperHandler->__construct(new Dumper);
+ $reflector = new \ReflectionClass($this->dumperHandler);
+ $optionsProp = $reflector->getProperty('dumper');
+ $optionsProp->setAccessible(true);
+ $this->assertTrue($optionsProp->getValue($this->dumperHandler) instanceof Dumper);
+ }
+
+
+
+ /**
+ * @covers \Dallgoot\Yaml\DumperHandlers::dumpScalar
+ */
+ public function testDumpScalar()
+ {
+ $this->assertEquals('.inf', $this->dumperHandler->dumpScalar(\INF));
+ $this->assertEquals('-.inf', $this->dumperHandler->dumpScalar(-\INF));
+ $this->assertEquals('true', $this->dumperHandler->dumpScalar(true));
+ $this->assertEquals('false', $this->dumperHandler->dumpScalar(false));
+ $this->assertEquals('.nan', $this->dumperHandler->dumpScalar(\NAN));
+ $this->assertEquals('0.4500', $this->dumperHandler->dumpScalar(0.45));
+ $this->assertEquals('0.1235', $this->dumperHandler->dumpScalar(0.123456));
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\DumperHandlers::dumpCompound
+ */
+ // public function testDumpCompoundException()
+ // {
+ // $callable = function () {
+ // return false;
+ // };
+ // $this->expectException(\Exception::class);
+ // $dumpCompound = new \ReflectionMethod($this->dumperHandler, 'dumpCompound');
+ // $dumpCompound->setAccessible(true);
+ // $dumpCompound->invoke($this->dumperHandler, $callable, 0);
+ // }
+ /**
+ * @covers \Dallgoot\Yaml\DumperHandlers::dumpCompound
+ * @todo implement these tests using new DumperHandlers methods
+ */
+ // public function testDumpCompound()
+ // {
+ // $dumpCompound = new \ReflectionMethod($this->dumperHandler, 'dumpCompound');
+ // $dumpCompound->setAccessible(true);
+ // $yamlObject = new YamlObject(0);
+ // $yamlObject->a = 1;
+ // $this->assertEquals('a: 1', $dumpCompound->invoke($this->dumperHandler, $yamlObject, 0));
+ // // unset($yamlObject->a);
+ // // $yamlObject[0] = 'a';
+ // $this->assertEquals('- a', $dumpCompound->invoke($this->dumperHandler, ['a'], 0));
+ // $compact = new Compact([1, 2, 3]);
+ // $this->assertEquals('[1, 2, 3]', $dumpCompound->invoke($this->dumperHandler, $compact, 0));
+ // $o = new \stdClass;
+ // $o->a = 1;
+ // $compact = new Compact($o);
+ // $this->assertEquals('{a: 1}', $dumpCompound->invoke($this->dumperHandler, $compact, 0));
+ // $this->assertEquals("- 1\n- 2\n- 3", $dumpCompound->invoke($this->dumperHandler, [1, 2, 3], 0));
+ // $tagged = new Tagged('!!str', 'somestring');
+ // $this->assertEquals("!!str somestring", $dumpCompound->invoke($this->dumperHandler, $tagged, 0));
+ // }
+
+ /**
+ * @covers \Dallgoot\Yaml\DumperHandlers::dumpCompact
+ */
+ public function testDumpCompact()
+ {
+ $this->assertEquals("[1, 2, 3]", $this->dumperHandler->dumpCompact(new Compact([1, 2, 3]), 0));
+ $o = new Compact([]);
+ $o->a = 1;
+ $o->b = [1, 2];
+ $o->c = new \stdClass;
+ $o->c->ca = 1;
+ $this->assertEquals("{a: 1, b: [1, 2], c: {ca: 1}}", $this->dumperHandler->dumpCompact($o, 0));
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\DumperHandlers::dumpString
+ */
+ public function testDumpString()
+ {
+ $this->assertEquals('abc ', $this->dumperHandler->dumpString(' abc ', 0));
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\DumperHandlers::dumpTagged
+ */
+ public function testDumpTagged()
+ {
+ $taggedStr = new Tagged('!!str', 'somestring');
+ $this->assertEquals("!!str somestring", $this->dumperHandler->dumpTagged($taggedStr, 0));
+ $taggedOmap = new Tagged('!!omap', [1, 2]);
+ $expected = <<assertEquals($expected, $this->dumperHandler->dumpTagged($taggedOmap, 0));
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/units/DumperTest.php b/classes/vendor/81x/dallgoot/yaml/tests/units/DumperTest.php
new file mode 100644
index 0000000..81fe1c9
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/units/DumperTest.php
@@ -0,0 +1,97 @@
+.
+ * @license https://opensource.org/licenses/MIT The MIT license.
+ * @link https://github.com/dallgoot/yaml
+ * @since File available since Release 1.0.0
+ *
+ * @covers \Dallgoot\Yaml\Dumper
+ */
+class DumperTest extends TestCase
+{
+ /**
+ * @var Dumper $dumper An instance of "Dumper" to test.
+ */
+ private $dumper;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp(): void
+ {
+ /** @todo Maybe add some arguments to this constructor */
+ $this->dumper = new Dumper();
+ }
+ /**
+ * @covers \Dallgoot\Yaml\DumperHandlers::__construct
+ */
+ public function test__construct()
+ {
+ $this->dumper->__construct(1);
+ $reflector = new \ReflectionClass($this->dumper);
+ $optionsProp = $reflector->getProperty('options');
+ $optionsProp->setAccessible(true);
+ $this->assertEquals(1, $optionsProp->getValue($this->dumper));
+ }
+ /**
+ * @covers \Dallgoot\Yaml\Dumper::toString
+ */
+ public function testToString(): void
+ {
+ $this->assertEquals("- 1\n- 2\n- 3", $this->dumper->toString([1, 2, 3]));
+ $this->assertEquals("--- some text\n", $this->dumper->toString('some text'));
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Dumper::toFile
+ */
+ public function testToFile(): void
+ {
+ $filename = 'dumperTest.yml';
+ $result = $this->dumper->toFile($filename, [1, 2, 3]);
+ $this->assertTrue($result);
+ $this->assertEquals("- 1\n- 2\n- 3", file_get_contents($filename));
+ unlink($filename);
+ }
+ /**
+ * @covers \Dallgoot\Yaml\Dumper::dump
+ */
+ public function testDump()
+ {
+ $this->assertEquals('', $this->dumper->dump(null, 0));
+ $this->assertEquals('stream', $this->dumper->dump(fopen(__FILE__, 'r'), 0));
+ $this->assertEquals('str', $this->dumper->dump('str', 0));
+ $this->assertEquals('- 1', $this->dumper->dump([1], 0, false, true));
+ $o = new \stdClass;
+ $o->prop = 1;
+ $this->assertEquals('prop: 1', $this->dumper->dump($o, 0, false, true));
+ }
+
+
+ /**
+ * @covers \Dallgoot\Yaml\Dumper::dumpYamlObject
+ */
+ public function testDumpYamlObject()
+ {
+ $dumpYamlObject = new \ReflectionMethod($this->dumper, 'dumpYamlObject');
+ $dumpYamlObject->setAccessible(true);
+ $yamlObject = new YamlObject(0);
+ $yamlObject->a = 1;
+ $this->assertEquals('a: 1', $dumpYamlObject->invoke($this->dumper, $yamlObject, 0));
+ unset($yamlObject->a);
+ $yamlObject[0] = 'a';
+ $this->assertEquals('- a', $dumpYamlObject->invoke($this->dumper, $yamlObject, 0));
+ }
+
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/units/LoaderTest.php b/classes/vendor/81x/dallgoot/yaml/tests/units/LoaderTest.php
new file mode 100644
index 0000000..5d98f2b
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/units/LoaderTest.php
@@ -0,0 +1,243 @@
+.
+ * @license https://opensource.org/licenses/MIT The MIT license.
+ * @link https://github.com/dallgoot/yaml
+ * @since File available since Release 1.0.0
+ *
+ * @covers \Dallgoot\Yaml\Loader
+ */
+class LoaderTest extends TestCase
+{
+ /**
+ * @var Loader $loader An instance of "Loader" to test.
+ */
+ private $loader;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp(): void
+ {
+ $this->loader = new Loader();
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Loader::__construct
+ */
+ public function testConstruct(): void
+ {
+ $this->expectException(\Exception::class);
+ $this->loader->__construct("non sense string");
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Loader::load
+ * @covers \Dallgoot\Yaml\Loader::getSourceGenerator
+ */
+ public function testLoad(): void
+ {
+ $this->assertEquals($this->loader, $this->loader->load(__DIR__ . '/../definitions/parsing_tests.yml'));
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Loader::load
+ */
+ public function testLoadNoFile(): void
+ {
+ $this->expectException(\Exception::class);
+ $this->loader->load('./non_existent_file');
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Loader::load
+ * @todo : make sure this tests covers the last Exception in method
+ */
+ // public function testLoadNoRights(): void
+ // {
+ // if (strpos('microsoft', php_uname())) {
+ // $this->markTestSkipped(
+ // "this test won't work on WSL Windows"
+ // );
+ // }
+ // $this->expectException(\Exception::class);
+ // $fileName = "./notreadable";
+ // touch($fileName);
+ // chmod($fileName, 0222);
+ // $this->loader->load($fileName);
+ // unlink($fileName);
+ // }
+
+ /**
+ * @covers \Dallgoot\Yaml\Loader::getSourceGenerator
+ */
+ public function testGetSourceGenerator(): void
+ {
+ $method = new \ReflectionMethod($this->loader, 'getSourceGenerator');
+ $method->setAccessible(true);
+ $result = $method->invoke($this->loader, '');
+ $this->assertTrue($result instanceof \Generator, 'getSourceGenerator is NOT a \\Generator');
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Loader::getSourceGenerator
+ */
+ public function testGetSourceGeneratorException(): void
+ {
+ $this->expectException(\Exception::class);
+ $method = new \ReflectionMethod($this->loader, 'getSourceGenerator');
+ $method->setAccessible(true);
+ $generator = $method->invoke($this->loader, null);
+ $generator->next();
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Loader::getSourceGenerator
+ */
+ // public function testGetSourceGeneratorExceptionOnNoSource(): void
+ // {
+ // $this->expectException(\Exception::class);
+ // $method = new \ReflectionMethod($this->loader, 'getSourceGenerator');
+ // $method->setAccessible(true);
+ // $property = new \ReflectionProperty($this->loader, 'content');
+ // $property->setAccessible(true);
+ // $property->setValue($this->loader, \SplFixedArray::fromArray([]));
+ // $generator = $method->invoke($this->loader, null);
+ // $generator->next();
+ // }
+
+ /**
+ * @covers \Dallgoot\Yaml\Loader::parse
+ */
+ public function testParse(): void
+ {
+ $result = $this->loader->parse("key: keyvalue\n other string\notherkey: othervalue");
+ $this->assertTrue($result instanceof YamlObject);
+ $result = $this->loader->parse('key: keyvalue');
+ $this->assertTrue($result instanceof YamlObject);
+ $multidoc = $this->loader->parse("---\nkey1: key1value\n---\nkey1: key1value\n");
+ $this->assertTrue(is_array($multidoc), 'result is NOT a multi-documents (ie. array)');
+ $this->assertTrue($multidoc[0] instanceof YamlObject, 'array #0 is NOT a YamlObject');
+ $this->assertTrue($multidoc[1] instanceof YamlObject, 'array #1 is NOT a YamlObject');
+ $yamlMapping = $this->loader->parse("key:\n insidekey: value\nlessindent: value");
+ $this->assertTrue($yamlMapping instanceof YamlObject);
+ $this->assertTrue(\property_exists($yamlMapping, 'key'));
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Loader::parse
+ */
+ public function testParseWithError(): void
+ {
+ $this->expectException(\Exception::class);
+ // fails because theres no NodeSetKey before
+ $result = $this->loader->parse(' :: keyvalue');
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Loader::_attachBlankLines
+ */
+ public function testAttachBlankLines(): void
+ {
+ $rootNode = new Root();
+ $blankNode = new Blank('', 1);
+ // NodeBlank get private method
+ $reflectorBlank = new \ReflectionClass($blankNode);
+ $method = $reflectorBlank->getMethod('setParent');
+ $method->setAccessible(true);
+ // blankNode->setParent($rootNode) : sets the parent for blankNode
+ $method->invoke($blankNode, $rootNode);
+ $this->assertTrue($rootNode->value->count() === 0, 'rootNode has a child yet');
+ // Loader get private property '_blankBuffer'
+ $reflectorLoader = new \ReflectionClass($this->loader);
+ $blankBufferProp = $reflectorLoader->getProperty('_blankBuffer');
+ $blankBufferProp->setAccessible(true);
+ $this->assertTrue(count($blankBufferProp->getValue($this->loader)) === 0, '_blankbuffer is NOT empty');
+ // set _blankbuffer : ie. add blankNode
+ $blankBufferProp->setValue($this->loader, [$blankNode]);
+ $this->assertTrue(count($blankBufferProp->getValue($this->loader)) === 1, 'blankBuffer has NO content');
+ $this->assertTrue($blankBufferProp->getValue($this->loader)[0] instanceof Blank, 'blankBuffer has NO nodeBlank');
+ //attach to parents => add child to parent
+ // $this->loader->attachBlankLines($rootNode);
+ $attachBlankLinesMethod = new \ReflectionMethod($this->loader, '_attachBlankLines');
+ $attachBlankLinesMethod->setAccessible(true);
+ $attachBlankLinesMethod->invoke($this->loader, $rootNode);
+ $this->assertTrue(count($blankBufferProp->getValue($this->loader)) === 0, '_blankbuffer is NOT empty');
+ $this->assertTrue($rootNode->value->count() === 1, 'rootnode has NO child');
+ $this->assertTrue($rootNode->value->offsetGet(0) instanceof Blank, 'rootnode child is NOT a blanknode');
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Loader::needsSpecialProcess
+ * @todo assert a true value also
+ */
+ public function testNeedsSpecialProcess(): void
+ {
+ $needsSpecialProcessMethod = new \ReflectionMethod($this->loader, 'needsSpecialProcess');
+ $needsSpecialProcessMethod->setAccessible(true);
+ $current = new Scalar('some text', 1);
+ $previous = new Root();
+ // $this->assertFalse($this->loader->needsSpecialProcess($current, $previous));
+ $this->assertFalse($needsSpecialProcessMethod->invoke($this->loader, $current, $previous));
+ $current = new Blank('', 1);
+ $previous = new Root();
+ // $this->assertTrue($this->loader->needsSpecialProcess($current, $previous));
+ $this->assertTrue($needsSpecialProcessMethod->invoke($this->loader, $current, $previous));
+ $previous = new Key('key: "partial value', 1);
+ $current = new Scalar(' end of partial value"', 2);
+ // $this->assertTrue($this->loader->needsSpecialProcess($current, $previous));
+ $this->assertTrue($needsSpecialProcessMethod->invoke($this->loader, $current, $previous));
+ $current = new Partial(' " oddly quoted');
+ // $this->assertFalse($this->loader->needsSpecialProcess($current, $previous));
+ $this->assertFalse($needsSpecialProcessMethod->invoke($this->loader, $current, $previous));
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Loader::onError
+ */
+ public function testOnError(): void
+ {
+ $this->expectException(\Exception::class);
+ $generator = function () {
+ yield 1 => 'this is the first line';
+ yield 2 => 'this is the second line';
+ };
+ // $this->loader->onError(new \Exception, $generator());
+ $onErrorMethod = new \ReflectionMethod($this->loader, 'onError');
+ $onErrorMethod->setAccessible(true);
+ $onErrorMethod->invoke(new \Exception, $generator());
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Loader::onError
+ */
+ public function testOnErrorButNoException(): void
+ {
+ $this->loader = new Loader(null, Loader::NO_PARSING_EXCEPTIONS);
+ $generator = function () {
+ yield 1 => 'this is the first line';
+ yield 2 => 'this is the second line';
+ };
+ $onErrorMethod = new \ReflectionMethod($this->loader, 'onError');
+ $onErrorMethod->setAccessible(true);
+ $this->assertEquals(null, $onErrorMethod->invoke($this->loader, new \Exception, $generator()->key()));
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/units/NodeFactoryTest.php b/classes/vendor/81x/dallgoot/yaml/tests/units/NodeFactoryTest.php
new file mode 100644
index 0000000..04335aa
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/units/NodeFactoryTest.php
@@ -0,0 +1,302 @@
+.
+ * @license https://opensource.org/licenses/MIT The MIT license.
+ * @link https://github.com/dallgoot/yaml
+ * @since File available since Release 1.0.0
+ *
+ * @covers \Dallgoot\Yaml\NodeFactory
+ */
+class NodeFactoryTest extends TestCase
+{
+ /**
+ * @var NodeFactory $nodeFactory An instance of "NodeFactory" to test.
+ */
+ private $nodeFactory;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp(): void
+ {
+ /** @todo Maybe add some arguments to this constructor */
+ $this->nodeFactory = new NodeFactory();
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\NodeFactory::get
+ */
+ public function testGet(): void
+ {
+ $this->assertTrue($this->nodeFactory::get('', 1) instanceof Blank, 'Not a NodeBlank');
+ $this->assertTrue($this->nodeFactory::get(' ...', 1) instanceof DocEnd, 'Not a NodeDocEnd');
+ $this->assertTrue($this->nodeFactory::get(' key : value', 1) instanceof Key, 'Not a NodeKey');
+ $this->assertTrue($this->nodeFactory::get('$qsd = 3213', 1) instanceof Scalar, 'Not a NodeScalar');
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\NodeFactory::get
+ */
+ public function testGetException(): void
+ {
+ $this->expectException(\ParseError::class);
+ $this->nodeFactory::get('%INVALID_DIRECTIVE xxx', 1);
+ }
+
+
+ /**
+ * @covers \Dallgoot\Yaml\NodeFactory::onDirective
+ */
+ public function testOnDirectiveParseError(): void
+ {
+ $this->expectException(\ParseError::class);
+ $reflector = new \ReflectionClass($this->nodeFactory);
+ $method = $reflector->getMethod('onDirective');
+ $method->setAccessible(true);
+ $nodeString = '%INVALID_DIRECTIVE xxx';
+ $method->invoke(null, $nodeString, 1);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\NodeFactory::onQuoted
+ */
+ public function testOnQuoted(): void
+ {
+ $reflector = new \ReflectionClass($this->nodeFactory);
+ $method = $reflector->getMethod('onQuoted');
+ $method->setAccessible(true);
+ $nodeString = ' "double quoted" ';
+ $this->assertTrue(
+ $method->invoke(null, $nodeString[0], $nodeString, 1) instanceof Quoted,
+ 'Not a NodeQuoted'
+ );
+ $nodeString = " 'simple quoted' ";
+ $this->assertTrue(
+ $method->invoke(null, $nodeString[0], $nodeString, 1) instanceof Quoted,
+ 'Not a NodeQuoted'
+ );
+ $nodeString = " 'simple partial ";
+ $this->assertTrue(
+ $method->invoke(null, $nodeString[0], $nodeString, 1) instanceof Partial,
+ 'Not a NodePartial'
+ );
+ $nodeString = '" double partial ';
+ $this->assertTrue(
+ $method->invoke(null, $nodeString[0], $nodeString, 1) instanceof Partial,
+ 'Not a NodePartial'
+ );
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\NodeFactory::onCharacter
+ */
+ public function testOnSetElement(): void
+ {
+ $reflector = new \ReflectionClass($this->nodeFactory);
+ $method = $reflector->getMethod('onCharacter');
+ $method->setAccessible(true);
+ $nodeString = ' ? some setkey ';
+ $this->assertTrue(
+ $method->invoke(null, trim($nodeString)[0], $nodeString, 1) instanceof SetKey,
+ 'Not a NodeSetKey'
+ );
+ $nodeString = ' : some setvalue ';
+ $this->assertTrue(
+ $method->invoke(null, trim($nodeString)[0], $nodeString, 1) instanceof SetValue,
+ 'Not a NodeSetValue'
+ );
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\NodeFactory::onCompact
+ */
+ public function testOnCompactJSON(): void
+ {
+ $method = new \ReflectionMethod($this->nodeFactory, 'onCompact');
+ $method->setAccessible(true);
+ $nodeString = '["a","b","c"]';
+ $this->assertTrue(
+ $method->invoke(null, $nodeString, 1) instanceof JSON,
+ 'Not a NodeJSON'
+ );
+ }
+ /**
+ * @covers \Dallgoot\Yaml\NodeFactory::onCompact
+ */
+ public function testOnCompactJSONMAPPING(): void
+ {
+ $method = new \ReflectionMethod($this->nodeFactory, 'onCompact');
+ $method->setAccessible(true);
+ $nodeString = '{"key":"value","key1":"value1"}';
+ $this->assertTrue($method->invoke(null, $nodeString, 1) instanceof JSON, 'Not a NodeJSON');
+ }
+ /**
+ * @covers \Dallgoot\Yaml\NodeFactory::onCompact
+ */
+ public function testOnCompactMAPPING(): void
+ {
+ $method = new \ReflectionMethod($this->nodeFactory, 'onCompact');
+ $method->setAccessible(true);
+ $nodeString = '{ key : value , key1 : value1 }';
+ $output = $method->invoke(null, $nodeString, 1);
+ $this->assertTrue($output instanceof CompactMapping, get_class($output) . ' instead of a NodeCompactMapping');
+ }
+ /**
+ * @covers \Dallgoot\Yaml\NodeFactory::onCompact
+ */
+ public function testOnCompactSEQUENCE(): void
+ {
+ $method = new \ReflectionMethod($this->nodeFactory, 'onCompact');
+ $method->setAccessible(true);
+ $nodeString = '[a,b,c]';
+ $output = $method->invoke(null, $nodeString, 1);
+ $this->assertTrue($output instanceof CompactSequence, get_class($output) . ' instead of a NodeCompactSequence');
+ }
+ /**
+ * @covers \Dallgoot\Yaml\NodeFactory::onCompact
+ */
+ public function testOnCompactPartialMapping(): void
+ {
+ $method = new \ReflectionMethod($this->nodeFactory, 'onCompact');
+ $method->setAccessible(true);
+ $nodeString = ' { a: b, ';
+ $output = $method->invoke(null, $nodeString, 1);
+ $this->assertTrue($output instanceof Partial, get_class($output) . ' instead of a Partial');
+ }
+ /**
+ * @covers \Dallgoot\Yaml\NodeFactory::onCompact
+ */
+ public function testOnCompactPartialSequence(): void
+ {
+ $method = new \ReflectionMethod($this->nodeFactory, 'onCompact');
+ $method->setAccessible(true);
+ $nodeString = ' [ a, b, ';
+ $output = $method->invoke(null, $nodeString, 1);
+ $this->assertTrue($output instanceof Partial, get_class($output) . ' instead of a Partial');
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\NodeFactory::onHyphen
+ */
+ public function testOnHyphen(): void
+ {
+ $reflector = new \ReflectionClass($this->nodeFactory);
+ $method = $reflector->getMethod('onHyphen');
+ $method->setAccessible(true);
+ $nodeString = '- ';
+ $this->assertTrue(
+ $method->invoke(null, $nodeString, 1) instanceof Item,
+ 'Not a NodeItem'
+ );
+ $nodeString = '-- ';
+ $this->assertTrue(
+ $method->invoke(null, $nodeString, 1) instanceof Scalar,
+ 'Not a NodeScalar'
+ );
+ $nodeString = '---';
+ $this->assertTrue(
+ $method->invoke(null, $nodeString, 1) instanceof DocStart,
+ 'Not a NodeDocStart'
+ );
+ $nodeString = ' - simpleitem ';
+ $this->assertTrue(
+ $method->invoke(null, $nodeString, 1) instanceof Item,
+ 'Not a NodeItem'
+ );
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\NodeFactory::onNodeAction
+ */
+ public function testOnNodeAction(): void
+ {
+ $method = new \ReflectionMethod($this->nodeFactory, 'onNodeAction');
+ $method->setAccessible(true);
+ $nodeString = '***';
+ $this->assertTrue(
+ $method->invoke(null, $nodeString, 1) instanceof Scalar,
+ 'Not a NodeScalar'
+ );
+ $nodeString = '&emptyanchor';
+ $this->assertTrue(
+ $method->invoke(null, $nodeString, 1) instanceof Anchor,
+ 'Not a NodeAnchor' . get_class($method->invoke(null, $nodeString, 1))
+ );
+ $nodeString = '&anchor [1,2,3]';
+ $this->assertTrue(
+ $method->invoke(null, $nodeString, 1) instanceof Anchor,
+ 'Not a NodeAnchor'
+ );
+ $nodeString = '*anchorcall ';
+ $this->assertTrue(
+ $method->invoke(null, $nodeString, 1) instanceof Anchor,
+ 'Not a NodeAnchor'
+ );
+
+ }
+
+ public function testOnCharacter(): void
+ {
+ $method = new \ReflectionMethod($this->nodeFactory, 'onCharacter');
+ $method->setAccessible(true);
+ $nodeString = '!!emptytag';
+ $tagged = $method->invoke(null, $nodeString[0], $nodeString, 1);
+ $this->assertTrue($tagged instanceof Tag, 'Not a NodeTag');
+ $this->assertEquals('!!emptytag', $tagged->tag);
+ $nodeString = '!!str 345 ';
+ $result = $method->invoke(null, $nodeString[0], $nodeString, 1);
+ $this->assertTrue($result instanceof Tag,'Not a NodeTag:'.get_class($result));
+ $this->assertEquals('!!str', $result->tag);
+ }
+
+
+ /**
+ * @covers \Dallgoot\Yaml\NodeFactory::onCharacter
+ */
+ public function testOnLiteral(): void
+ {
+ $method = new \ReflectionMethod($this->nodeFactory, 'onCharacter');
+ $method->setAccessible(true);
+ $nodeString = ' |- ';
+ $this->assertTrue(
+ $method->invoke(null, trim($nodeString)[0], $nodeString, 1) instanceof Literal,
+ 'Not a NodeLit'
+ );
+ $nodeString = ' >+ ';
+ $this->assertTrue(
+ $method->invoke(null, trim($nodeString)[0], $nodeString, 1) instanceof LiteralFolded,
+ 'Not a NodeLitFolded'
+ );
+ }
+
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/units/NodeListTest.php b/classes/vendor/81x/dallgoot/yaml/tests/units/NodeListTest.php
new file mode 100644
index 0000000..1d5ceba
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/units/NodeListTest.php
@@ -0,0 +1,178 @@
+.
+ * @license https://opensource.org/licenses/MIT The MIT license.
+ * @link https://github.com/dallgoot/yaml
+ * @since File available since Release 1.0.0
+ *
+ * @covers \Dallgoot\Yaml\NodeList
+ */
+class NodeListTest extends TestCase
+{
+ /**
+ * @var NodeList $nodeList An instance of "NodeList" to test.
+ */
+ private $nodeList;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp(): void
+ {
+ $this->nodeList = new NodeList(new Blank('', 1));
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\NodeList::__construct
+ */
+ public function testConstruct(): void
+ {
+ $this->assertTrue($this->nodeList->count() === 1);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\NodeList::has
+ */
+ public function testHas(): void
+ {
+ $this->assertTrue($this->nodeList->has('Blank'));
+ $this->assertFalse($this->nodeList->has('Item'));
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\NodeList::hasContent
+ */
+ public function testHasContent(): void
+ {
+ $this->assertFalse($this->nodeList->hasContent());
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\NodeList::hasContent
+ */
+ public function testHasContentWithDocStart(): void
+ {
+ $docstartNode = new DocStart('--- some value', 1);
+ $this->nodeList->push($docstartNode);
+ $this->assertTrue($this->nodeList->hasContent());
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\NodeList::push
+ */
+ public function testPush(): void
+ {
+ $this->assertTrue(is_null($this->nodeList->type));
+ $this->nodeList->push(new Item('- item', 1));
+ $this->assertEquals($this->nodeList->type, $this->nodeList::SEQUENCE);
+
+ $this->nodeList = new NodeList;
+ $this->nodeList->push(new Key(' key: value', 1));
+ $this->assertEquals($this->nodeList->type, $this->nodeList::MAPPING);
+
+ $this->nodeList = new NodeList;
+ $this->nodeList->push(new SetKey(' ? simplekey ', 1));
+ $this->assertEquals($this->nodeList->type, $this->nodeList::SET);
+
+ $this->nodeList = new NodeList;
+ $this->nodeList->push(new Scalar('whatever string', 1));
+ $this->assertEquals($this->nodeList->type, $this->nodeList::MULTILINE);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\NodeList::checkTypeCoherence
+ */
+ public function testCheckTypeCoherence(): void
+ {
+ $this->assertFalse($this->nodeList->checkTypeCoherence(null));
+ $this->assertFalse($this->nodeList->checkTypeCoherence(0));
+ $this->assertTrue($this->nodeList->checkTypeCoherence(4));
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\NodeList::build
+ * @depends testPush
+ */
+ public function testBuild(): void
+ {
+ $keyNode = new Key('key: keyvalue', 1);
+ $itemNode = new Item(' - itemvalue', 1);
+ $scalarNode = new Scalar('a string value', 1);
+ //expect object
+ $this->nodeList->push($keyNode);
+ $this->assertTrue(is_object($this->nodeList->build()));
+ // expect array
+ $this->nodeList = new NodeList;
+ $this->nodeList->push($itemNode);
+ $this->assertTrue(is_array($this->nodeList->build()));
+ // expect string
+ $this->nodeList = new NodeList;
+ $this->nodeList->push($scalarNode);
+ $this->assertTrue(is_string($this->nodeList->build()));
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\NodeList::buildList
+ */
+ public function testBuildList(): void
+ {
+ $arr = [];
+ $this->assertEquals($arr, $this->nodeList->buildList($arr));
+ $obj = new \stdClass;
+ $this->assertEquals($obj, $this->nodeList->buildList($obj));
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\NodeList::buildMultiline
+ */
+ public function testBuildMultiline(): void
+ {
+ //test when empty
+ $this->assertEquals(1, $this->nodeList->count(), 'NodeList count is wrong (+1 Nodeblank on setUp)');
+ $this->assertEquals('', $this->nodeList->buildMultiline(), 'buildMultiline did not return a string');
+ //test with one child
+ $this->nodeList->push(new Scalar('some string', 2));
+ $this->assertEquals(2, $this->nodeList->count(), 'NodeList does NOT contain 2 children');
+ $this->assertEquals('some string', $this->nodeList->buildMultiline(), 'buildMultiline failed with 2 children');
+ //test with one child AND one blank
+ $this->nodeList->push(new Blank('', 2));
+ $this->nodeList->push(new Scalar('other string', 3));
+ $this->assertEquals(4, $this->nodeList->count(), 'NodeList does NOT contain 2 children');
+ $this->assertEquals("some string\nother string", $this->nodeList->buildMultiline(), 'buildMultiline failed with 2 children');
+ //test with two child
+ $this->nodeList->push(new Scalar('and some other string', 3));
+ $this->assertEquals(5, $this->nodeList->count(), 'NodeList does NOT contain 3 nodes');
+ $this->assertEquals("some string\nother string and some other string", $this->nodeList->buildMultiline(), "buildMultiline failed to provide correct string");
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\NodeList::filterComment
+ * @todo problem with building comments on YamlObject since theres no Root NodeGeneric and no Yaml object
+ */
+ public function testFilterComment(): void
+ {
+ $this->nodeList->push(new Comment('# this is a comment', 1));
+ $this->assertEquals(2, $this->nodeList->count());
+ $filtered = $this->nodeList->filterComment();
+ $this->assertEquals(1, $filtered->count());
+ $this->assertEquals(2, $this->nodeList->count());
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/units/RegexTest.php b/classes/vendor/81x/dallgoot/yaml/tests/units/RegexTest.php
new file mode 100644
index 0000000..338d225
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/units/RegexTest.php
@@ -0,0 +1,91 @@
+.
+ * @license https://opensource.org/licenses/MIT The MIT license.
+ * @link https://github.com/dallgoot/yaml
+ * @since File available since Release 1.0.0
+ *
+ * @covers \Dallgoot\Yaml\Regex
+ */
+class RegexTest extends TestCase
+{
+ /**
+ * @var Regex $regex An instance of "Regex" to test.
+ */
+ private $regex;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp(): void
+ {
+ /** @todo Maybe add some arguments to this constructor */
+ $this->regex = new Regex();
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Regex::isDate
+ */
+ public function testIsDate(): void
+ {
+ $this->assertTrue($this->regex::isDate('2002-12-14'));
+ $this->assertTrue($this->regex::isDate('2002/12/14'));
+ $this->assertTrue($this->regex::isDate('2001-12-15T02:59:43.1Z'));
+ $this->assertTrue($this->regex::isDate('2001-12-14 21:59:43.10 -5'));
+ $this->assertTrue($this->regex::isDate('2001-12-14t21:59:43.10-05:00'));
+ //not conforing dates
+ $this->assertFalse($this->regex::isDate('20-12-2004'));
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Regex::isDate
+ */
+ public function testIsDateWithNoString(): void
+ {
+ $this->expectException(\TypeError::class);
+ $this->assertFalse($this->regex::isDate(null));
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Regex::isNumber
+ */
+ public function testIsNumber(): void
+ {
+ $this->assertTrue($this->regex::isNumber('0o45'));
+ $this->assertTrue($this->regex::isNumber('0xa7'));
+ $this->assertTrue($this->regex::isNumber('123'));
+ $this->assertTrue($this->regex::isNumber('123.456'));
+ $this->assertTrue($this->regex::isNumber('.6'));
+ // not standard numbers
+ $this->assertFalse($this->regex::isNumber('123.45.6'));
+ $this->assertFalse($this->regex::isNumber('0x'));
+ $this->assertFalse($this->regex::isNumber('0o'));
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Regex::isProperlyQuoted
+ */
+ public function testIsProperlyQuoted(): void
+ {
+ $this->assertTrue($this->regex::isProperlyQuoted(' " " '));
+ $this->assertTrue($this->regex::isProperlyQuoted(' " \" " '));
+ $this->assertTrue($this->regex::isProperlyQuoted(" ' ' "));
+ $this->assertTrue($this->regex::isProperlyQuoted(" ' \' ' "));
+ $this->assertTrue($this->regex::isProperlyQuoted("' 'a' 'b' '"));
+ $this->assertTrue($this->regex::isProperlyQuoted('" "a" "b" "'));
+ $this->assertTrue($this->regex::isProperlyQuoted('" \"a\" \'b\' "'));
+
+ $this->assertFalse($this->regex::isProperlyQuoted('" \"a\" \'b\' '));
+
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/units/YamlTest.php b/classes/vendor/81x/dallgoot/yaml/tests/units/YamlTest.php
new file mode 100644
index 0000000..067bd3e
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/units/YamlTest.php
@@ -0,0 +1,110 @@
+.
+ * @license https://opensource.org/licenses/MIT The MIT license.
+ * @link https://github.com/dallgoot/yaml
+ * @since File available since Release 1.0.0
+ *
+ * @covers \Dallgoot\Yaml\Yaml */
+class YamlTest extends TestCase
+{
+ /**
+ * @var Yaml $yaml An instance of "Yaml" to test.
+ */
+ private $yaml;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp(): void
+ {
+ /** @todo Maybe add some arguments to this constructor */
+ $this->yaml = new Yaml();
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Yaml::parse
+ */
+ public function testParse(): void
+ {
+ $yaml = "- 1\n- 2\n- 3\n";
+ $this->assertTrue($this->yaml::parse($yaml) instanceof YamlObject);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Yaml::parse
+ */
+ public function testParseException(): void
+ {
+ $this->expectException(\Exception::class);
+ $this->yaml::parse('::');
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Yaml::parseFile
+ */
+ public function testParseFile(): void
+ {
+ $this->assertTrue($this->yaml::parseFile(__DIR__ . "/../definitions/parsing_tests.yml") instanceof YamlObject);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Yaml::parseFile
+ */
+ public function testParseFileException(): void
+ {
+ $this->expectException(\Exception::class);
+ $this->yaml::parseFile('ssh:example.com');
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Yaml::dump
+ */
+ public function testDump(): void
+ {
+ $this->assertEquals("- 1\n- 2\n- 3", $this->yaml::dump([1, 2, 3]));
+ $this->assertEquals("--- some text\n", $this->yaml::dump('some text'));
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Yaml::dump
+ */
+ public function testDumpException(): void
+ {
+ $this->expectException(\Throwable::class);
+ $this->yaml::dump(null);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Yaml::dumpFile
+ */
+ public function testDumpFile(): void
+ {
+ $filename = 'dumperTest.yml';
+ $result = $this->yaml::dumpFile($filename, [1, 2, 3]);
+ $this->assertTrue($result);
+ $this->assertEquals("- 1\n- 2\n- 3", file_get_contents($filename));
+ unlink($filename);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Yaml::dumpFile
+ */
+ public function testDumpFileException(): void
+ {
+ $this->expectException(\Throwable::class);
+ $this->yaml::dumpFile('someFileName', null);
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/AnchorTest.php b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/AnchorTest.php
new file mode 100644
index 0000000..4b9997d
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/AnchorTest.php
@@ -0,0 +1,77 @@
+.
+ * @license https://opensource.org/licenses/MIT The MIT license.
+ * @link https://github.com/dallgoot/yaml
+ * @since File available since Release 1.0.0
+ *
+ * @covers \Dallgoot\Yaml\Nodes\Anchor
+ */
+class AnchorTest extends TestCase
+{
+ /**
+ * @var Anchor $nodeAnchor An instance of "Nodes\Anchor" to test.
+ */
+ private $nodeAnchor;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp(): void
+ {
+ $this->nodeAnchor = new Anchor('&aaa sometext', 1);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Anchor::build
+ */
+ public function testBuild(): void
+ {
+ $yamlObject = new YamlObject(0);
+ $rootNode = new Root();
+ $rootNode->add($this->nodeAnchor);
+ $reflector = new \ReflectionClass($rootNode);
+ $buildFinal = $reflector->getMethod('buildFinal');
+ $buildFinal->setAccessible(true);
+ $buildFinal->invoke($rootNode, $yamlObject);
+ $this->assertEquals('sometext', $this->nodeAnchor->build());
+ // test exsting reference
+ $anchorValue = '12345';
+ $yamlObject = new YamlObject(0);
+ $rootNode = new Root();
+ $this->nodeAnchor = new Anchor('*aaa', 1);
+
+ $rootNode->add($this->nodeAnchor);
+ $yamlObject->addReference('aaa', $anchorValue);
+ $buildFinal = new \ReflectionMethod($rootNode, 'buildFinal');
+ $buildFinal->setAccessible(true);
+ $buildFinal->invoke($rootNode, $yamlObject);
+ $this->assertEquals('12345', $this->nodeAnchor->build());
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Anchor::isAwaitingChild
+ */
+ public function testIsAwaitingChild(): void
+ {
+ $uselessNode = new Blank('', 1);
+ $this->assertFalse($this->nodeAnchor->IsAwaitingChild($uselessNode));
+ $this->nodeAnchor->value = null;
+ $this->assertTrue($this->nodeAnchor->IsAwaitingChild($uselessNode));
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/BlankTest.php b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/BlankTest.php
new file mode 100644
index 0000000..305b8b9
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/BlankTest.php
@@ -0,0 +1,106 @@
+.
+ * @license https://opensource.org/licenses/MIT The MIT license.
+ * @link https://github.com/dallgoot/yaml
+ * @since File available since Release 1.0.0
+ *
+ * @covers \Dallgoot\Yaml\Nodes\Blank
+ */
+class BlankTest extends TestCase
+{
+ /**
+ * @var Blank $nodeBlank An instance of "Nodes\Blank" to test.
+ */
+ private $nodeBlank;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp(): void
+ {
+ /** @todo Maybe add some arguments to this constructor */
+ $this->nodeBlank = new Blank('', 1);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Blank::add
+ */
+ public function testAdd(): void
+ {
+ $nodeLit = new Literal('|', 1);
+ $this->assertTrue(is_null($nodeLit->value));
+ $nodeLit->add($this->nodeBlank);
+ $this->assertTrue($nodeLit->value->offsetGet(0) === $this->nodeBlank);
+ $nodeScalar = new Scalar('sometext', 3);
+ $this->nodeBlank->add($nodeScalar);
+ $this->assertTrue($nodeLit->value instanceof NodeList);
+ $this->assertTrue($nodeLit->value->offsetGet(1) === $nodeScalar);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Blank::specialProcess
+ */
+ public function testSpecialProcess(): void
+ {
+ $blankBuffer = [];
+ $previousScalarParent = new Literal('|-', 1);
+ $previousScalar = new Scalar(' sometext', 2);
+ $previousScalarParent->add($previousScalar);
+ $this->assertTrue($this->nodeBlank->specialProcess($previousScalar, $blankBuffer));
+ $this->assertEquals($this->nodeBlank, $blankBuffer[0]);
+ $this->assertEquals($this->nodeBlank->getParent(), $previousScalarParent);
+ $blankBuffer = [];
+ $keyNode = new Key(' somelit: |', 1);
+ $this->assertTrue($this->nodeBlank->specialProcess($keyNode, $blankBuffer));
+ $this->assertEquals($this->nodeBlank, $blankBuffer[0]);
+ $this->assertEquals($this->nodeBlank->getParent(), $keyNode->value);
+
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Blank::build
+ */
+ public function testBuild(): void
+ {
+ $this->assertEquals("\n", $this->nodeBlank->build());
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Blank::getTargetOnEqualIndent
+ */
+ public function testGetTargetOnEqualIndent(): void
+ {
+ $uselessNode = new Scalar('sometext with no indent', 3);
+ $blankBuffer = [];
+ $keyNode = new Key(' somelit: |', 1);
+ $keyNode->add($this->nodeBlank);
+ $this->assertEquals($this->nodeBlank->getTargetOnEqualIndent($uselessNode), $keyNode);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Blank::getTargetOnMoreIndent
+ */
+ public function testGetTargetOnMoreIndent(): void
+ {
+ $uselessNode = new Scalar('sometext with no indent', 3);
+ $blankBuffer = [];
+ $keyNode = new Key(' somelit: |', 1);
+ $keyNode->add($this->nodeBlank);
+ $this->assertEquals($this->nodeBlank->getTargetOnMoreIndent($uselessNode), $keyNode); }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/CommentTest.php b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/CommentTest.php
new file mode 100644
index 0000000..09a4458
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/CommentTest.php
@@ -0,0 +1,69 @@
+.
+ * @license https://opensource.org/licenses/MIT The MIT license.
+ * @link https://github.com/dallgoot/yaml
+ * @since File available since Release 1.0.0
+ *
+ * @covers \Dallgoot\Yaml\Nodes\Comment
+ */
+class CommentTest extends TestCase
+{
+ /**
+ * @var Comment $nodeComment An instance of "Nodes\Comment" to test.
+ */
+ private $nodeComment;
+
+ private $commentLine = 5;
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp(): void
+ {
+ /** @todo Maybe add some arguments to this constructor */
+ $this->nodeComment = new Comment('#this is a comment for test', $this->commentLine);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Comment::specialProcess
+ */
+ public function testSpecialProcess(): void
+ {
+ $keyNode = new Key(' key: keyvalue',1);
+ $rootNode = new Root();
+ $rootNode->add($keyNode);
+ $blankBuffer = [];
+ $this->assertTrue($this->nodeComment->specialProcess($keyNode, $blankBuffer));
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Comment::build
+ */
+ public function testBuild(): void
+ {
+ $yamlObject = new YamlObject(0);
+ $rootNode = new Root;
+ $reflector = new \ReflectionClass($rootNode);
+ $method = $reflector->getMethod('buildFinal');
+ $method->setAccessible(true);
+ $method->invoke($rootNode, $yamlObject);
+ $rootNode->add($this->nodeComment);
+ $this->nodeComment->build();
+ $this->assertEquals($yamlObject->getComment($this->commentLine), $this->nodeComment->raw);
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/CompactMappingTest.php b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/CompactMappingTest.php
new file mode 100644
index 0000000..1712902
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/CompactMappingTest.php
@@ -0,0 +1,72 @@
+.
+ * @license https://opensource.org/licenses/MIT The MIT license.
+ * @link https://github.com/dallgoot/yaml
+ * @since File available since Release 1.0.0
+ *
+ * @covers \Dallgoot\Yaml\Nodes\CompactMapping
+ */
+class CompactMappingTest extends TestCase
+{
+ /**
+ * @var CompactMapping $nodeCompactMapping An instance of "Nodes\CompactMapping" to test.
+ */
+ private $nodeCompactMapping;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp(): void
+ {
+ $this->nodeCompactMapping = new CompactMapping(" {a : 123, b: abc } ", 42);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\CompactMapping::__construct
+ */
+ public function testConstruct(): void
+ {
+ $children = $this->nodeCompactMapping->value;
+ $this->assertTrue($children instanceof NodeList);
+ $this->assertTrue($children[0] instanceof Key);
+ $this->assertTrue($children[0]->value instanceof Scalar);
+ $this->assertTrue($children[1] instanceof Key);
+ $this->assertTrue($children[1]->value instanceof Scalar);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\CompactMapping::build
+ */
+ public function testBuild(): void
+ {
+ $result = $this->nodeCompactMapping->build();
+ $this->assertTrue($result instanceof Compact);
+ $this->assertTrue(property_exists($result, 'a'));
+ $this->assertEquals(123, $result->a);
+ $this->assertTrue(property_exists($result, 'b'));
+ $this->assertEquals('abc', $result->b);
+ $this->nodeCompactMapping->value = null;
+ $this->assertTrue(is_null($this->nodeCompactMapping->build()));
+ // test single value converted to list
+ $this->nodeCompactMapping = new CompactMapping(" { a : 123 } ", 42);
+ $result = $this->nodeCompactMapping->build();
+ $this->assertTrue($result instanceof Compact);
+ $this->assertTrue(property_exists($result, 'a'));
+ $this->assertEquals(123, $result->a);
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/CompactSequenceTest.php b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/CompactSequenceTest.php
new file mode 100644
index 0000000..774b2f4
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/CompactSequenceTest.php
@@ -0,0 +1,77 @@
+.
+ * @license https://opensource.org/licenses/MIT The MIT license.
+ * @link https://github.com/dallgoot/yaml
+ * @since File available since Release 1.0.0
+ *
+ * @covers \Dallgoot\Yaml\Nodes\CompactSequence
+ */
+class CompactSequenceTest extends TestCase
+{
+ /**
+ * @var CompactSequence $nodeCompactSequence An instance of "Nodes\CompactSequence" to test.
+ */
+ private $nodeCompactSequence;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp(): void
+ {
+ $this->nodeCompactSequence = new CompactSequence(" [ 1, ad, [456] ]", 42);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\CompactSequence::__construct
+ */
+ public function testConstruct(): void
+ {
+ $children = $this->nodeCompactSequence->value;
+ $this->assertTrue($children instanceof NodeList);
+ $this->assertTrue($children[0] instanceof Item);
+ $this->assertTrue($children[0]->value instanceof Scalar);
+ $this->assertTrue($children[1] instanceof Item);
+ $this->assertTrue($children[1]->value instanceof Scalar);
+ $this->assertTrue($children[2] instanceof item);
+ $this->assertTrue($children[2]->value instanceof JSON);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\CompactSequence::build
+ */
+ public function testBuild(): void
+ {
+ $result = $this->nodeCompactSequence->build();
+ $this->assertTrue($result instanceof Compact);
+ $this->assertArrayHasKey(0, $result);
+ $this->assertEquals(1, $result[0]);
+ $this->assertArrayHasKey(1, $result);
+ $this->assertEquals('ad', $result[1]);
+ $this->assertArrayHasKey(2, $result);
+ $this->assertEquals([456], $result[2]);
+ $this->nodeCompactSequence->value = null;
+ $this->assertTrue(is_null($this->nodeCompactSequence->build()));
+ //test single NodeGeneric absorbed in NodeList
+ $this->nodeCompactSequence = new CompactSequence(" [ [456] ]", 42);
+ $result = $this->nodeCompactSequence->build();
+ $this->assertTrue($result instanceof Compact);
+ $this->assertArrayHasKey(0, $result);
+ $this->assertEquals([456], $result[0]);
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/DirectiveTest.php b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/DirectiveTest.php
new file mode 100644
index 0000000..90b3887
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/DirectiveTest.php
@@ -0,0 +1,73 @@
+.
+ * @license https://opensource.org/licenses/MIT The MIT license.
+ * @link https://github.com/dallgoot/yaml
+ * @since File available since Release 1.0.0
+ *
+ * @covers \Dallgoot\Yaml\Nodes\Directive
+ */
+class DirectiveTest extends TestCase
+{
+ /**
+ * @var NodeDirective $nodeDirective An instance of "Nodes\Directive" to test.
+ */
+ private $nodeDirective;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp(): void
+ {
+ /** @todo Maybe add some arguments to this constructor */
+ $this->nodeDirective = new Directive('%YAML 1.2');
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Directive::build
+ */
+ public function testBuild(): void
+ {
+ $this->assertTrue(is_null($this->nodeDirective->build()));
+ $this->nodeDirective = new Directive('%TAG ! tag:clarkevans.com,2002:');
+ $rootNode = new Root;
+ $rootNode->add($this->nodeDirective);
+ $yamlObject = new YamlObject(0);
+ $rootNode->build($yamlObject);
+ $this->assertEquals('tag:clarkevans.com,2002:', TagFactory::$schemaHandles['!']);
+ }
+
+ public function testBuildError(): void
+ {
+ $this->expectException(\ParseError::class);
+ $this->nodeDirective = new Directive('%TAG ! tag:clarkevans.com,2002:');
+ $directive2 = new Directive('%TAG ! tag:clarkevans.com,2002:');
+ $rootNode = new Root;
+ $rootNode->add($this->nodeDirective);
+ $yamlObject = new YamlObject(0);
+ $rootNode->build($yamlObject);
+ }
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Directive::add
+ */
+ public function testAdd(): void
+ {
+ $uselessNode = new Blank('', 2);
+ $this->assertTrue($this->nodeDirective->add($uselessNode) === $uselessNode);
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/DocEndTest.php b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/DocEndTest.php
new file mode 100644
index 0000000..a7e69b1
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/DocEndTest.php
@@ -0,0 +1,43 @@
+.
+ * @license https://opensource.org/licenses/MIT The MIT license.
+ * @link https://github.com/dallgoot/yaml
+ * @since File available since Release 1.0.0
+ *
+ * @covers \Dallgoot\Yaml\Nodes\DocEnd
+ */
+class DocEndTest extends TestCase
+{
+ /**
+ * @var DocEnd $nodeDocEnd An instance of "Nodes\DocEnd" to test.
+ */
+ private $nodeDocEnd;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp(): void
+ {
+ /** @todo Maybe add some arguments to this constructor */
+ $this->nodeDocEnd = new DocEnd('...');
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\DocEnd::build
+ */
+ public function testBuild(): void
+ {
+ $this->assertTrue(is_null($this->nodeDocEnd->build()));
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/DocStartTest.php b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/DocStartTest.php
new file mode 100644
index 0000000..b7a034f
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/DocStartTest.php
@@ -0,0 +1,133 @@
+.
+ * @license https://opensource.org/licenses/MIT The MIT license.
+ * @link https://github.com/dallgoot/yaml
+ * @since File available since Release 1.0.0
+ *
+ * @covers \Dallgoot\Yaml\Nodes\DocStart
+ */
+class DocStartTest extends TestCase
+{
+ /**
+ * @var DocStart $nodeDocStart An instance of "Nodes\DocStart" to test.
+ */
+ private $nodeDocStart;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp(): void
+ {
+ /** @todo Maybe check arguments of this constructor. */
+ $this->nodeDocStart = new DocStart("---", 42);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\DocStart::__construct
+ */
+ public function testConstruct(): void
+ {
+ $this->assertTrue(is_null($this->nodeDocStart->value));
+ $this->nodeDocStart = new DocStart("--- sometext", 42);
+ $this->assertTrue($this->nodeDocStart->value instanceof Scalar);
+ $this->nodeDocStart = new DocStart("--- !sometag", 42);
+ $this->assertTrue($this->nodeDocStart->value instanceof Tag);
+ $this->nodeDocStart = new DocStart("--- #some comment", 42);
+ $this->assertTrue($this->nodeDocStart->value instanceof Comment);
+ $this->nodeDocStart = new DocStart("--- &someanchor", 42);
+ $this->assertTrue($this->nodeDocStart->value instanceof Anchor);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\DocStart::add
+ */
+ public function testAdd(): void
+ {
+ $blankNode = new Blank('', 2);
+ $this->assertEquals($blankNode, $this->nodeDocStart->add($blankNode));
+ $this->assertEquals($blankNode, $this->nodeDocStart->value);
+ $nodeTag = new Tag('!tagname', 2);
+ $this->nodeDocStart->value = null;
+ $this->nodeDocStart->add($nodeTag);
+ $this->assertEquals($nodeTag, $this->nodeDocStart->value);
+ $this->nodeDocStart->add($blankNode);
+ $this->assertEquals($nodeTag, $this->nodeDocStart->value);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\DocStart::build
+ */
+ public function testBuild(): void
+ {
+ $yamlObject = new YamlObject(0);
+ $this->assertTrue(is_null($this->nodeDocStart->build($yamlObject)));
+ $this->nodeDocStart->add(new Tag('!', 2));
+ $this->nodeDocStart->build($yamlObject);
+ $this->assertTrue($yamlObject->isTagged());
+ $this->nodeDocStart->value = null;
+ $nodeLit = new LiteralFolded('>-', 1);
+ $nodeScalar1 = new Scalar(' some text', 2);
+ $nodeScalar2 = new Scalar(' other text', 3);
+ $nodeLit->add($nodeScalar1);
+ $nodeLit->add($nodeScalar2);
+ $this->nodeDocStart->add($nodeLit);
+ $this->assertTrue(is_null($this->nodeDocStart->build($yamlObject)));
+ $this->assertEquals("some text other text", ''.$yamlObject);
+
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\DocStart::isAwaitingChild
+ */
+ public function testIsAwaitingChild(): void
+ {
+ $uselessNode = new Blank('', 1);
+ $this->assertFalse($this->nodeDocStart->isAwaitingChild($uselessNode));
+
+ $this->nodeDocStart->value = new Anchor('&anchor anchorDefinedWithValue', 1);
+ $this->assertTrue($this->nodeDocStart->isAwaitingChild($uselessNode));
+
+ $this->nodeDocStart->value = new Literal('|+', 1);
+ $this->assertTrue($this->nodeDocStart->isAwaitingChild($uselessNode));
+
+ $this->nodeDocStart->value = new LiteralFolded('>-', 1);
+ $this->assertTrue($this->nodeDocStart->isAwaitingChild($uselessNode));
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\DocStart::getTargetOnEqualIndent
+ */
+ public function testGetTargetOnEqualIndent(): void
+ {
+ $blank = new Blank('', 1);
+ $rootNode = new Root();
+ $rootNode->add($this->nodeDocStart);
+ $this->assertEquals($rootNode, $this->nodeDocStart->GetTargetOnEqualIndent($blank));
+
+ $nodeLitFolded = new LiteralFolded('>-', 1);
+ $this->nodeDocStart->value = $nodeLitFolded;
+ $this->assertEquals($nodeLitFolded, $this->nodeDocStart->GetTargetOnEqualIndent($blank));
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/ItemTest.php b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/ItemTest.php
new file mode 100644
index 0000000..422648d
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/ItemTest.php
@@ -0,0 +1,174 @@
+.
+ * @license https://opensource.org/licenses/MIT The MIT license.
+ * @link https://github.com/dallgoot/yaml
+ * @since File available since Release 1.0.0
+ *
+ * @covers \Dallgoot\Yaml\Nodes\Item
+ */
+class ItemTest extends TestCase
+{
+ /**
+ * @var Item $nodeItem An instance of "Nodes\Item" to test.
+ */
+ private $nodeItem;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp(): void
+ {
+ /** @todo Maybe check arguments of this constructor. */
+ $this->nodeItem = new Item(" -", 42);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Item::__construct
+ */
+ public function testConstruct(): void
+ {
+ $this->assertTrue(is_null($this->nodeItem->value));
+ $this->nodeItem = new Item(" - itemvalue", 42);
+ $this->assertTrue($this->nodeItem->value instanceof Scalar);
+ $this->nodeItem = new Item(" - keyinside: keyvalue", 42);
+ $this->assertTrue($this->nodeItem->value instanceof Key);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Item::add
+ */
+ public function testAdd(): void
+ {
+ $this->assertTrue(is_null($this->nodeItem->value));
+ //
+ $this->nodeItem = new Item(' - keyinside: keyvalue', 1);
+ $keyNode = new Key(' anotherkey: anothervalue', 3);
+ $this->nodeItem->add($keyNode);
+ $this->assertTrue($this->nodeItem->value instanceof NodeList);
+ //
+ $this->nodeItem = new Item(' - keyinside:', 3);
+ $keyNode = new Key(' childkey: anothervalue', 4);
+ $this->nodeItem->add($keyNode);
+ $keyinside = $this->nodeItem->value;
+ $this->assertEquals($keyNode, $keyinside->value);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Item::add
+ */
+ public function testAddException(): void
+ {
+ $this->expectException(\ParseError::class);
+ $this->nodeItem = new Item(' - keyinside: keyvalue', 1);
+ $keyNode = new Key(' anotherkey: anothervalue', 3);
+ $this->nodeItem->add($keyNode);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Item::getTargetOnEqualIndent
+ */
+ public function testGetTargetOnEqualIndent(): void
+ {
+ $rootNode = new Root;
+ $rootNode->add($this->nodeItem);
+ $itemNode = new Item('- item2', 1);
+ $this->assertEquals($rootNode, $this->nodeItem->getTargetOnEqualIndent($itemNode));
+ //
+ $rootNode = new Root;
+ $this->nodeItem = new Item('- sameindentitem', 3);
+ $keyNode = new Key('key_with_no_indent_a_sequence:', 1);
+ $rootNode->add($keyNode);
+ $keyNode->add($this->nodeItem);
+ // $this->nodeItem->add($keyNode);
+ $itemNode2 = new Item('- item_with_no_indent: 123', 2);
+ $parent = $this->nodeItem->getTargetOnEqualIndent($itemNode2);
+ $this->assertEquals($rootNode, $parent);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Item::getTargetOnMoreIndent
+ */
+ public function testGetTargetOnMoreIndent(): void
+ {
+ $blankNode = new Blank('', 1);
+ $this->assertEquals($this->nodeItem, $this->nodeItem->getTargetOnMoreIndent($blankNode));
+ $keyNode = new Key('key:', 1);
+ $this->nodeItem->add($keyNode);
+ $this->assertEquals($keyNode, $this->nodeItem->getTargetOnMoreIndent($blankNode));
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Item::build
+ */
+ public function testBuild(): void
+ {
+ // no parent
+ $this->assertEquals([null], $this->nodeItem->build());
+ $parent = [];
+ $this->assertEquals(null, $this->nodeItem->build($parent));
+ $this->assertEquals([0 => null], $parent);
+ $parent = new YamlObject(0);
+ $this->assertEquals(null, $this->nodeItem->build($parent));
+ $this->assertArrayHasKey(0, $parent);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Item::build
+ */
+ public function testBuildWhenParentIsString()
+ {
+ $this->expectException(\Exception::class);
+ $parent = '';
+ $this->nodeItem->build($parent);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Item::build
+ */
+ public function testBuildWhenParentIsObject()
+ {
+ $this->expectException(\Exception::class);
+ $parent = new \stdClass;
+ $this->nodeItem->build($parent);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Item::isAwaitingChild
+ */
+ public function testIsAwaitingChild(): void
+ {
+ $blankNode = new Blank('', 1);
+ $this->assertTrue($this->nodeItem->isAwaitingChild($blankNode));
+ //
+ $setkeyNode = new SetKey('? setkey', 1);
+ $setvalueNode = new SetValue(': setvalue', 2);
+ $this->nodeItem->add($setkeyNode);
+ $this->assertTrue($this->nodeItem->isAwaitingChild($setvalueNode));
+ //
+ $this->nodeItem->value = null;
+ $keyNode = new Key(' key: ', 42);
+ $scalarNode = new Scalar(' some text', 43);
+ $this->nodeItem->add($keyNode);
+ $this->assertTrue($this->nodeItem->isAwaitingChild($scalarNode));
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/JSONTest.php b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/JSONTest.php
new file mode 100644
index 0000000..f67e438
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/JSONTest.php
@@ -0,0 +1,43 @@
+.
+ * @license https://opensource.org/licenses/MIT The MIT license.
+ * @link https://github.com/dallgoot/yaml
+ * @since File available since Release 1.0.0
+ *
+ * @covers \Dallgoot\Yaml\Nodes\JSON
+ */
+class JSONTest extends TestCase
+{
+ /**
+ * @var JSON $nodeJSON An instance of "Nodes\JSON" to test.
+ */
+ private $nodeJSON;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp(): void
+ {
+ /** @todo Maybe add some arguments to this constructor */
+ $this->nodeJSON = new JSON(' [1,2,3]',1);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\JSON::build
+ */
+ public function testBuild(): void
+ {
+ $this->assertEquals([1,2,3], $this->nodeJSON->build());
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/KeyTest.php b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/KeyTest.php
new file mode 100644
index 0000000..36aa217
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/KeyTest.php
@@ -0,0 +1,211 @@
+.
+ * @license https://opensource.org/licenses/MIT The MIT license.
+ * @link https://github.com/dallgoot/yaml
+ * @since File available since Release 1.0.0
+ *
+ * @covers \Dallgoot\Yaml\Nodes\Key
+ */
+class KeyTest extends TestCase
+{
+ /**
+ * @var Key $nodeKey An instance of "Nodes\Key" to test.
+ */
+ private $nodeKey;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp(): void
+ {
+ /** @todo Maybe check arguments of this constructor. */
+ $this->nodeKey = new Key("key: value", 1);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Key::__construct
+ */
+ public function testConstruct(): void
+ {
+ $this->assertTrue($this->nodeKey->value instanceof Scalar);
+ $this->nodeKey = new Key("key:", 1);
+ $this->assertTrue(is_null($this->nodeKey->value));
+ $this->nodeKey = new Key("key: ", 1);
+ $this->assertTrue(is_null($this->nodeKey->value));
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Key::__construct
+ */
+ public function testConstructException(): void
+ {
+ $this->expectException(\ParseError::class);
+ $this->nodeKey->__construct('not a key at all and no matches', 1);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Key::setIdentifier
+ */
+ public function testSetIdentifier(): void
+ {
+ $reflector = new \ReflectionClass($this->nodeKey);
+ $identifier = $reflector->getProperty('identifier');
+ $identifier->setAccessible(true);
+
+ $this->nodeKey->setIdentifier('newkey');
+ $this->assertEquals('newkey', $identifier->getValue($this->nodeKey));
+
+ $this->nodeKey->setIdentifier('!!str 1.2');
+ $this->assertEquals(null, $this->nodeKey->tag);
+ $this->assertEquals('1.2', $identifier->getValue($this->nodeKey));
+
+ $this->nodeKey->setIdentifier('&anchor 1.2');
+ $this->assertEquals(null, $this->nodeKey->anchor);
+ $this->assertEquals('1.2', $identifier->getValue($this->nodeKey));
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Key::setIdentifier
+ */
+ public function testSetIdentifierAsEmptyString()
+ {
+ $this->expectException(\ParseError::class);
+ $this->nodeKey->setIdentifier('');
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Key::add
+ */
+ public function testAdd(): void
+ {
+ $scalarNode = new Scalar('sometext', 2);
+ $this->nodeKey = new Key("key:", 1);
+ $this->nodeKey->add($scalarNode);
+ $this->assertEquals($scalarNode, $this->nodeKey->value);
+ //
+ $this->nodeKey = new Key("key: |", 1);
+ $this->nodeKey->add($scalarNode);
+ $nodeLit = $this->nodeKey->value;
+ $this->assertEquals($scalarNode, $nodeLit->value->offsetGet(0));
+ //
+ $this->nodeKey = new Key("key: >", 1);
+ $this->nodeKey->add($scalarNode);
+ $nodeLitFolded = $this->nodeKey->value;
+ $this->assertEquals($scalarNode, $nodeLitFolded->value->offsetGet(0));
+ //
+ $this->nodeKey = new Key("key: &anchor", 1);
+ $this->nodeKey->add($scalarNode);
+ $nodeAnchor = $this->nodeKey->value;
+ $this->assertEquals($scalarNode, $nodeAnchor->value);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Key::getTargetOnEqualIndent
+ */
+ public function testGetTargetOnEqualIndent(): void
+ {
+ $nodeItem = new Item(' - an_item', 2);
+ $this->nodeKey = new Key(" key:", 1);
+ $this->assertEquals($this->nodeKey, $this->nodeKey->getTargetOnEqualIndent($nodeItem));
+
+ $blankNode = new Blank('', 3);
+ $rootNode = new Root;
+ $rootNode->add($this->nodeKey);
+ $this->assertEquals($this->nodeKey->getParent(), $this->nodeKey->getTargetOnEqualIndent($blankNode));
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Key::getTargetOnMoreIndent
+ */
+ public function testGetTargetOnMoreIndent(): void
+ {
+ $blankNode = new Blank('', 2);
+ $this->assertEquals($this->nodeKey, $this->nodeKey->getTargetOnMoreIndent($blankNode));
+
+ $this->nodeKey = new Key(" key: &anchor |", 1);
+ $anchorNode = $this->nodeKey->value;
+ $this->assertEquals($anchorNode->value, $this->nodeKey->getTargetOnMoreIndent($blankNode));
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Key::isAwaitingChild
+ */
+ public function testIsAwaitingChild(): void
+ {
+ $blankNode = new Blank('', 42);
+ $this->assertTrue($this->nodeKey->isAwaitingChild($blankNode));
+ //
+ $this->nodeKey->value = new Comment('# this is a comment', 1);
+ $this->assertTrue($this->nodeKey->isAwaitingChild($blankNode));
+ //
+ $this->nodeKey->value = new Scalar('this is a text', 1);
+ $this->assertTrue($this->nodeKey->isAwaitingChild($blankNode));
+ //
+ $this->nodeKey->value = new Item(' - item', 1);
+ $this->assertTrue($this->nodeKey->isAwaitingChild(new Item(' - item2', 2)));
+ //
+ $this->nodeKey->value = new Key(' key1:', 1);
+ $this->assertTrue($this->nodeKey->isAwaitingChild(new Key(' key2:', 2)));
+ //
+ $this->nodeKey->value = new Literal(' |', 1);
+ $this->assertTrue($this->nodeKey->isAwaitingChild(new Key(' key2:', 2)));
+ //
+ $this->nodeKey->value = new Anchor(' &anchor', 1);
+ $this->assertTrue($this->nodeKey->isAwaitingChild(new Key(' key2:', 2)));
+ //
+ $this->nodeKey->value = new Anchor(' &anchor already have a value', 1);
+ $this->assertFalse($this->nodeKey->isAwaitingChild(new Key(' key2:', 2)));
+ }
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Key::build
+ */
+ public function testBuild(): void
+ {
+ $keyTagged = new Key('!!str 1.2: value', 1);
+ $built = $keyTagged->build();
+ $this->assertTrue(property_exists($built, '1.2'));
+ $this->assertEquals('value', $built->{'1.2'});
+ //
+ $built = $this->nodeKey->build();
+ $this->assertTrue(property_exists($built, 'key'));
+ $this->assertEquals('value', $built->key);
+ //
+ $parent = new \stdClass;
+ $built = $this->nodeKey->build($parent);
+ $this->assertTrue(property_exists($parent, 'key'));
+ $this->assertEquals('value', $parent->key);
+ //
+ $parent = new YamlObject(0);
+ $built = $this->nodeKey->build($parent);
+ $this->assertTrue(property_exists($parent, 'key'));
+ $this->assertEquals('value', $parent->key);
+ //
+ $this->nodeKey->value = null;
+ $built = $this->nodeKey->build();
+ $this->assertTrue(property_exists($built, 'key'));
+ $this->assertEquals(null, $built->key);
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/LiteralFoldedTest.php b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/LiteralFoldedTest.php
new file mode 100644
index 0000000..ed495f3
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/LiteralFoldedTest.php
@@ -0,0 +1,59 @@
+.
+ * @license https://opensource.org/licenses/MIT The MIT license.
+ * @link https://github.com/dallgoot/yaml
+ * @since File available since Release 1.0.0
+ *
+ * @covers \Dallgoot\Yaml\Nodes\LiteralFolded
+ */
+class LiteralFoldedTest extends TestCase
+{
+ /**
+ * @var LiteralFolded $nodeLitFolded An instance of "Nodes\LiteralFolded" to test.
+ */
+ private $nodeLitFolded;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp(): void
+ {
+ /** @todo Maybe add some arguments to this constructor */
+ $this->nodeLitFolded = new LiteralFolded('>-', 1);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\LiteralFolded::getFinalString
+ */
+ public function testGetFinalString(): void
+ {
+ $line1 = new Scalar(' - with inside', 2);
+ $line1a = new Scalar(' two', 3);
+ $line1b = new Scalar(' children', 4);
+ $line2 = new Scalar(' some more indented text', 5);
+ $line3 = new Scalar(' other less indented text', 6);
+ $list = new NodeList;
+ $list->push($line1);
+ $list->push($line1a);
+ $list->push($line1b);
+ $list->push($line2);
+ $list->push($line3);
+ $this->assertEquals(
+ "- with inside\ntwo\nchildren\nsome more indented text\nother less indented text",
+ $this->nodeLitFolded->getFinalString($list));
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/LiteralTest.php b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/LiteralTest.php
new file mode 100644
index 0000000..735bd5c
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/LiteralTest.php
@@ -0,0 +1,53 @@
+.
+ * @license https://opensource.org/licenses/MIT The MIT license.
+ * @link https://github.com/dallgoot/yaml
+ * @since File available since Release 1.0.0
+ *
+ * @covers \Dallgoot\Yaml\Nodes\Literal
+ */
+class LiteralTest extends TestCase
+{
+ /**
+ * @var Literal $nodeLit An instance of "Nodes\Literal" to test.
+ */
+ private $nodeLit;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp(): void
+ {
+ /** @todo Maybe add some arguments to this constructor */
+ $this->nodeLit = new Literal('|', 1);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Literal::getFinalString
+ */
+ public function testGetFinalString(): void
+ {
+ $line1 = new Scalar(' some text', 2);
+ $line2 = new Scalar(' some more indented text', 3);
+ $line3 = new Scalar(' other less indented text', 4);
+ $list = new NodeList;
+ $list->push($line1);
+ $list->push($line2);
+ $list->push($line3);
+ $this->assertEquals("some text\n some more indented text\nother less indented text",
+ $this->nodeLit->getFinalString($list));
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/PartialTest.php b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/PartialTest.php
new file mode 100644
index 0000000..3d29984
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/PartialTest.php
@@ -0,0 +1,92 @@
+.
+ * @license https://opensource.org/licenses/MIT The MIT license.
+ * @link https://github.com/dallgoot/yaml
+ * @since File available since Release 1.0.0
+ *
+ * @covers \Dallgoot\Yaml\Nodes\Partial
+ */
+class PartialTest extends TestCase
+{
+ /**
+ * @var Partial $nodePartial An instance of "Nodes\Partial" to test.
+ */
+ private $nodePartial;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp(): void
+ {
+ $this->nodePartial = new Partial(' " partially quoted');
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Partial::specialProcess
+ */
+ public function testSpecialProcess(): void
+ {
+ $blankBuffer = [];
+ $node = new Scalar(' end of quoting"', 2);
+ $parent = new Scalar(' emptykey:', 1);
+ $parent->add($this->nodePartial);
+ $this->assertTrue($this->nodePartial->specialProcess($node, $blankBuffer));
+ $this->assertTrue($parent->value instanceof Quoted);
+ $this->assertEquals(" partially quoted end of quoting", $parent->value->build());
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Partial::specialProcess
+ */
+ public function testSpecialProcessWithPreviousLineFeed(): void
+ {
+ $this->nodePartial = new Partial(" ' partially quoted\n");
+ $blankBuffer = [];
+ $node = new Scalar(" end of quoting'", 2);
+ $parent = new Scalar(' emptykey:', 1);
+ $parent->add($this->nodePartial);
+ $this->assertTrue($this->nodePartial->specialProcess($node, $blankBuffer));
+ $this->assertTrue($parent->value instanceof Quoted);
+ $this->assertEquals(" partially quoted\nend of quoting", $parent->value->build());
+
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Partial::specialProcess
+ */
+ public function testSpecialProcessWithNodeBlank(): void
+ {
+ $blankBuffer = [];
+ $current = new Blank('', 2);
+ $parent = new Scalar(' emptykey:', 1);
+ $parent->add($this->nodePartial);
+ $this->assertTrue($this->nodePartial->specialProcess($current, $blankBuffer));
+ $this->assertTrue($parent->value instanceof Partial);
+ $this->assertEquals(" \" partially quoted\n", $parent->value->raw);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Partial::build
+ */
+ public function testBuild(): void
+ {
+ $this->expectException(\ParseError::class);
+ $this->nodePartial->build();
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/QuotedTest.php b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/QuotedTest.php
new file mode 100644
index 0000000..e7ca4fe
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/QuotedTest.php
@@ -0,0 +1,43 @@
+.
+ * @license https://opensource.org/licenses/MIT The MIT license.
+ * @link https://github.com/dallgoot/yaml
+ * @since File available since Release 1.0.0
+ *
+ * @covers \Dallgoot\Yaml\Nodes\Quoted
+ */
+class QuotedTest extends TestCase
+{
+ /**
+ * @var Quoted $nodeQuoted An instance of "Nodes\Quoted" to test.
+ */
+ private $nodeQuoted;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp(): void
+ {
+ /** @todo Maybe add some arguments to this constructor */
+ $this->nodeQuoted = new Quoted('"a quoted string"');
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Quoted::build
+ */
+ public function testBuild(): void
+ {
+ $this->assertEquals("a quoted string", $this->nodeQuoted->build());
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/RootTest.php b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/RootTest.php
new file mode 100644
index 0000000..22236ef
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/RootTest.php
@@ -0,0 +1,114 @@
+.
+ * @license https://opensource.org/licenses/MIT The MIT license.
+ * @link https://github.com/dallgoot/yaml
+ * @since File available since Release 1.0.0
+ *
+ * @covers \Dallgoot\Yaml\Nodes\Root
+ */
+class RootTest extends TestCase
+{
+ /**
+ * @var Root $nodeRoot An instance of "Nodes\Root" to test.
+ */
+ private $nodeRoot;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp(): void
+ {
+ /** @todo Maybe add some arguments to this constructor */
+ $this->nodeRoot = new Root();
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Root::__construct
+ */
+ public function testConstruct(): void
+ {
+ $this->assertTrue($this->nodeRoot->value instanceof NodeList);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Root::getParent
+ */
+ public function testGetParent(): void
+ {
+ $this->assertEquals($this->nodeRoot, $this->nodeRoot->getParent());
+ $keyNode = new Key(' falsekey:', 1);
+ $keyNode->add($this->nodeRoot);
+ $this->expectException(\ParseError::class);
+ $this->nodeRoot->getParent();
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Root::getRoot
+ */
+ public function testGetRoot(): void
+ {
+ $this->assertEquals($this->nodeRoot, $this->nodeRoot->getRoot());
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Root::getYamlObject
+ */
+ public function testYamlObjectAbsent()
+ {
+ $this->expectException(\Exception::class);
+ $this->nodeRoot->getYamlObject();
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Root::buildFinal
+ */
+ public function testBuildFinal(): void
+ {
+ $buildFinal = new \ReflectionMethod($this->nodeRoot, 'buildFinal');
+ $buildFinal->setAccessible(true);
+ $yamlObject = new YamlObject(0);
+ $result = $buildFinal->invoke($this->nodeRoot, $yamlObject);
+ $this->assertEquals($yamlObject, $result);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Root::getYamlObject
+ * @depends testBuildFinal
+ */
+ public function testYamlObjectPresent()
+ {
+ $buildFinal = new \ReflectionMethod($this->nodeRoot, 'buildFinal');
+ $buildFinal->setAccessible(true);
+ $yamlObject = new YamlObject(0);
+ $result = $buildFinal->invoke($this->nodeRoot, $yamlObject);
+
+ $this->assertEquals($yamlObject, $result);
+ $this->assertEquals($yamlObject, $this->nodeRoot->getYamlObject());
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Root::build
+ */
+ public function testBuild(): void
+ {
+ $yamlObject = new YamlObject(0);
+ $this->assertTrue($this->nodeRoot->build($yamlObject) instanceof YamlObject);
+ }
+
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/ScalarTest.php b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/ScalarTest.php
new file mode 100644
index 0000000..0ebe172
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/ScalarTest.php
@@ -0,0 +1,138 @@
+.
+ * @license https://opensource.org/licenses/MIT The MIT license.
+ * @link https://github.com/dallgoot/yaml
+ * @since File available since Release 1.0.0
+ *
+ * @covers \Dallgoot\Yaml\Nodes\Scalar
+ */
+class ScalarTest extends TestCase
+{
+ /**
+ * @var Scalar $nodeScalar An instance of "Nodes\Scalar" to test.
+ */
+ private $nodeScalar;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp(): void
+ {
+ $this->nodeScalar = new Scalar("a string to test", 42);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Scalar::__construct
+ */
+ public function testConstruct(): void
+ {
+ // only a Sclar Node
+ $this->assertTrue(is_null($this->nodeScalar->value));
+ // with a comment
+ $nodeScalar = new Scalar(' value # a comment', 1);
+ $this->assertTrue($nodeScalar->value instanceof NodeList);
+ $this->assertTrue($nodeScalar->value->offsetGet(0) instanceof Scalar);
+ $this->assertTrue($nodeScalar->value->offsetGet(1) instanceof Comment);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Scalar::build
+ */
+ public function testBuild(): void
+ {
+ $this->assertEquals("a string to test", $this->nodeScalar->build());
+ //
+ $this->nodeScalar->value = new Scalar('another string', 2);
+ $this->assertEquals('another string', $this->nodeScalar->build());
+ //
+ $this->nodeScalar->raw = "123";
+ $this->nodeScalar->value = null;
+ $this->assertEquals(123, $this->nodeScalar->build());
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Scalar::build
+ */
+ public function testBuildTagged(): void
+ {
+ $this->nodeScalar = new Scalar("123", 42);
+ $this->nodeScalar->tag = '!!str';
+ $this->assertEquals('123', $this->nodeScalar->build());
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Scalar::getTargetOnLessIndent
+ */
+ public function testGetTargetOnLessIndent(): void
+ {
+ $parent = new Key(' emptykey: |', 1);
+ $nodeScalar = new Scalar(' somestring', 2);
+ $parent->add($this->nodeScalar);
+ $this->assertEquals($parent, $this->nodeScalar->getTargetOnLessIndent($parent));
+ $this->assertTrue($this->nodeScalar->getParent() instanceof Literal);
+ //
+ $parent2 = new Key(' emptykey2:', 1);
+ $this->nodeScalar = new Scalar('somestring', 2);
+ $parent2->add($this->nodeScalar);
+ $blankNode = new Blank('', 3);
+ $this->assertEquals($parent2, $this->nodeScalar->getTargetOnLessIndent($blankNode));
+ $this->assertEquals($parent2, $this->nodeScalar->getParent());
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Scalar::getTargetOnMoreIndent
+ */
+ public function testGetTargetOnMoreIndent(): void
+ {
+ $parent = new Key(' emptykey:', 1);
+ $parent->add($this->nodeScalar);
+ $this->assertEquals($parent, $this->nodeScalar->getTargetOnMoreIndent($parent));
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Scalar::getScalar
+ */
+ public function testGetScalar(): void
+ {
+ $this->assertEquals($this->nodeScalar->getScalar('yes') , true);
+ $this->assertEquals($this->nodeScalar->getScalar('no') , false);
+ $this->assertEquals($this->nodeScalar->getScalar('true') , true);
+ $this->assertEquals($this->nodeScalar->getScalar('false'), false);
+ $this->assertEquals($this->nodeScalar->getScalar('null') , null);
+ $this->assertEquals($this->nodeScalar->getScalar('.inf') , \INF);
+ $this->assertEquals($this->nodeScalar->getScalar('-.inf'), -\INF);
+ $this->assertTrue(is_nan($this->nodeScalar->getScalar('.nan')));
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Scalar::getNumber
+ */
+ public function testGetNumber(): void
+ {
+ $reflector = new \ReflectionClass($this->nodeScalar);
+ $method = $reflector->getMethod('getNumber');
+ $method->setAccessible(true);
+ $this->assertTrue(is_numeric($method->invoke($this->nodeScalar, '132')));
+ $this->assertTrue(is_numeric($method->invoke($this->nodeScalar, '0x27')));
+ $this->assertTrue(is_numeric($method->invoke($this->nodeScalar, '0xaf')));
+ $this->assertTrue(is_float($method->invoke($this->nodeScalar, '132.123')));
+ $this->assertFalse(is_float($method->invoke($this->nodeScalar, '132.12.3')));
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/SetKeyTest.php b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/SetKeyTest.php
new file mode 100644
index 0000000..fa68b43
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/SetKeyTest.php
@@ -0,0 +1,69 @@
+.
+ * @license https://opensource.org/licenses/MIT The MIT license.
+ * @link https://github.com/dallgoot/yaml
+ * @since File available since Release 1.0.0
+ *
+ * @covers \Dallgoot\Yaml\Nodes\SetKey
+ */
+class SetKeyTest extends TestCase
+{
+ /**
+ * @var SetKey $nodeSetKey An instance of "Nodes\SetKey" to test.
+ */
+ private $nodeSetKey;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp(): void
+ {
+ /** @todo Maybe check arguments of this constructor. */
+ $this->nodeSetKey = new SetKey(" ? someStringKey", 42);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\SetKey::__construct
+ */
+ public function testConstruct(): void
+ {
+ $this->assertTrue($this->nodeSetKey->value instanceof Scalar);
+ $this->assertEquals('someStringKey', $this->nodeSetKey->value->build());
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\SetKey::build
+ */
+ public function testBuild(): void
+ {
+ $parent = new \stdClass;
+ $built = $this->nodeSetKey->build($parent);
+ $this->assertTrue(property_exists($parent, 'someStringKey'));
+ $this->assertEquals(null, $parent->someStringKey);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\SetKey::isAwaitingChild
+ */
+ public function testIsAwaitingChild(): void
+ {
+ $uselessNode = new Blank('', 1);
+ $this->assertFalse($this->nodeSetKey->isAwaitingChild($uselessNode));
+ $this->nodeSetKey->value = null;
+ $this->assertTrue($this->nodeSetKey->isAwaitingChild($uselessNode));
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/SetValueTest.php b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/SetValueTest.php
new file mode 100644
index 0000000..61f729c
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/SetValueTest.php
@@ -0,0 +1,70 @@
+.
+ * @license https://opensource.org/licenses/MIT The MIT license.
+ * @link https://github.com/dallgoot/yaml
+ * @since File available since Release 1.0.0
+ *
+ * @covers \Dallgoot\Yaml\Nodes\SetValue
+ */
+class SetValueTest extends TestCase
+{
+ /**
+ * @var SetValue $nodeSetValue An instance of "Nodes\SetValue" to test.
+ */
+ private $nodeSetValue;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp(): void
+ {
+ /** @todo Maybe check arguments of this constructor. */
+ $this->nodeSetValue = new SetValue(" : a string to test", 42);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\SetValue::__construct
+ */
+ public function testConstruct(): void
+ {
+ $this->assertTrue($this->nodeSetValue->value instanceof Scalar);
+ $this->assertEquals('a string to test', $this->nodeSetValue->value->build());
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\SetValue::build
+ */
+ public function testBuild(): void
+ {
+ $parent = new \stdClass;
+ $parent->lastKey = null;
+ $this->nodeSetValue->build($parent);
+ $this->assertTrue(property_exists($parent, 'lastKey'));
+ $this->assertEquals('a string to test', $parent->lastKey);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\SetValue::isAwaitingChild
+ */
+ public function testIsAwaitingChild(): void
+ {
+ $uselessNode = new Blank('', 1);
+ $this->assertFalse($this->nodeSetValue->isAwaitingChild($uselessNode));
+ $this->nodeSetValue->value = null;
+ $this->assertTrue($this->nodeSetValue->isAwaitingChild($uselessNode));
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/TagTest.php b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/TagTest.php
new file mode 100644
index 0000000..5701b0a
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/TagTest.php
@@ -0,0 +1,91 @@
+.
+ * @license https://opensource.org/licenses/MIT The MIT license.
+ * @link https://github.com/dallgoot/yaml
+ * @since File available since Release 1.0.0
+ *
+ * @covers \Dallgoot\Yaml\Nodes\Tag
+ */
+class TagTest extends TestCase
+{
+ /**
+ * @var Tag $nodeTag An instance of "Nodes\Tag" to test.
+ */
+ private $nodeTag;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp(): void
+ {
+ $this->nodeTag = new Tag('!!str 654',0);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Tag::isAwaitingChild
+ */
+ public function testIsAwaitingChild(): void
+ {
+ $uselessNode = new Blank('', 1);
+ $this->assertFalse($this->nodeTag->isAwaitingChild($uselessNode));
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Tag::getTargetOnEqualIndent
+ */
+ public function testGetTargetOnEqualIndent(): void
+ {
+ $uselessNode = new Blank('', 1);
+ $parent = new Key(' key:', 1);
+ $parent->add($this->nodeTag);
+ $this->assertEquals($parent, $this->nodeTag->getTargetOnEqualIndent($uselessNode));
+ // $this->nodeTag->value = null;
+ // $this->assertEquals($this->nodeTag, $this->nodeTag->getTargetOnEqualIndent($uselessNode));
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Tag::build
+ */
+ public function testBuild(): void
+ {
+ // test value tranformed
+ $parent = new Key(' key:',1);
+ $yamlObject = new YamlObject(0);
+
+ $parent->add($this->nodeTag);
+ $built = $this->nodeTag->build();
+ $this->assertEquals('654', $built);
+ // test apply tag to YamlObject (cause value is null and parent is a NodeRoot)
+ $rootNode = new Root();
+ $this->assertFalse($yamlObject->isTagged());
+ $rootNode->add($this->nodeTag);
+ //$this->nodeTag->value = null;
+ // add yamlObject to NodeRoot
+ $rootNode->build($yamlObject);// this triggers this->nodeTag->build
+ $this->assertFalse($yamlObject->isTagged());
+ // test "unknown" ag: must return a Tag object
+ $this->nodeTag = new Tag('!!unknown 654',1);
+ $built = $this->nodeTag->build($yamlObject);
+ $this->assertTrue($built instanceof Tagged);
+ $this->assertEquals("!!unknown", $built->tagName);
+ $this->assertEquals("654", $built->value);
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/abstract/ActionsTest.php b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/abstract/ActionsTest.php
new file mode 100644
index 0000000..c15f283
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/abstract/ActionsTest.php
@@ -0,0 +1,62 @@
+.
+ * @license https://opensource.org/licenses/MIT The MIT license.
+ * @link https://github.com/dallgoot/yaml
+ * @since File available since Release 1.0.0
+ *
+ * @covers \Dallgoot\Yaml\Nodes\Generic\Actions
+ */
+class ActionsTest extends TestCase
+{
+ /**
+ * @var Actions $nodeActions An instance of "Nodes\Actions" to test.
+ */
+ private $nodeActions;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp(): void
+ {
+ // $this->nodeActions = new Actions(" !!str sometext", 42);
+ $this->nodeActions = $this->getMockBuilder(Actions::class)
+ ->setConstructorArgs([" !!str sometext", 42])
+ ->getMockForAbstractClass();
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Generic\Actions::__construct
+ */
+ public function testConstruct(): void
+ {
+ //note : true behaviour for Tag is "testConstructWithTag"
+ $this->assertEquals("!!str", $this->nodeActions->anchor);
+ $this->assertTrue($this->nodeActions->value instanceof Scalar);
+ $this->assertEquals("sometext", $this->nodeActions->value->raw);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Generic\Actions::__construct
+ */
+ public function testConstructWithTag(): void
+ {
+ $tagNode = new Tag(" !!str sometext", 42);
+ $this->assertEquals("!!str", $tagNode->tag);
+ $this->assertTrue($tagNode->value instanceof Scalar);
+ $this->assertEquals("sometext", $tagNode->value->raw);
+ }
+
+}
\ No newline at end of file
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/abstract/LiteralsTest.php b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/abstract/LiteralsTest.php
new file mode 100644
index 0000000..a8721a7
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/abstract/LiteralsTest.php
@@ -0,0 +1,191 @@
+.
+ * @license https://opensource.org/licenses/MIT The MIT license.
+ * @link https://github.com/dallgoot/yaml
+ * @since File available since Release 1.0.0
+ *
+ * @covers \Dallgoot\Yaml\Nodes\Generic\Literals
+ */
+class LiteralsTest extends TestCase
+{
+ /**
+ * @var Literals $nodeLiterals An instance of "Nodes\Literals" to test.
+ */
+ private $nodeLiterals;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp(): void
+ {
+ /** @todo Maybe check arguments of this constructor. */
+ $this->nodeLiterals = $this->getMockBuilder(Literals::class)
+ ->setConstructorArgs(["|-", 42])
+ ->getMockForAbstractClass();
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Generic\Literals::__construct
+ */
+ public function testConstruct(): void
+ {
+ $this->nodeLiterals = $this->getMockBuilder(Literals::class)
+ ->setConstructorArgs(["|", 42])
+ ->getMockForAbstractClass();
+ $reflector = new \ReflectionClass($this->nodeLiterals);
+ $identifier = $reflector->getProperty('identifier');
+ $identifier->setAccessible(true);
+ $this->assertEquals(null, $identifier->getValue($this->nodeLiterals));
+ //
+ $this->nodeLiterals = $this->getMockBuilder(Literals::class)
+ ->setConstructorArgs(["|-", 42])
+ ->getMockForAbstractClass();
+ //
+ $this->nodeLiterals = $this->getMockBuilder(Literals::class)
+ ->setConstructorArgs(["|+", 42])
+ ->getMockForAbstractClass();
+ $this->assertEquals('+', $identifier->getValue($this->nodeLiterals));
+ //
+ $this->nodeLiterals = $this->getMockBuilder(Literals::class)
+ ->setConstructorArgs([">-", 42])
+ ->getMockForAbstractClass();
+ //
+ $this->nodeLiterals = $this->getMockBuilder(Literals::class)
+ ->setConstructorArgs([">+", 42])
+ ->getMockForAbstractClass();
+ $this->assertEquals('+', $identifier->getValue($this->nodeLiterals));
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Generic\Literals::add
+ */
+ public function testAdd(): void
+ {
+ $this->assertTrue(is_null($this->nodeLiterals->value));
+ $this->nodeLiterals->add(new Scalar(' some text', 2));
+ $this->assertTrue($this->nodeLiterals->value instanceof NodeList);
+ //
+ $this->nodeLiterals->value = null;
+ $falseItem = new Item(' - not an item', 2);
+
+ $this->assertTrue($this->nodeLiterals->add($falseItem) instanceof Scalar);
+ $this->assertFalse($this->nodeLiterals->value->offsetGet(0) instanceof Item);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Generic\Literals::litteralStripLeading
+ */
+ public function testLitteralStripLeading(): void
+ {
+ $list = new NodeList(new Blank('', 1));
+ $list->push(new Blank('', 2));
+ $stripLeading = new \ReflectionMethod(Literals::class, 'litteralStripLeading');
+ $stripLeading->setAccessible(true);
+ $stripLeading->invokeArgs(null, [&$list]);
+ $this->assertEquals(0, $list->count());
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Generic\Literals::litteralStripTrailing
+ */
+ public function testLitteralStripTrailing(): void
+ {
+ $list = new NodeList(new Blank('', 1));
+ $list->push(new Blank('', 2));
+ $stripTrailing = new \ReflectionMethod(Literals::class, 'litteralStripTrailing');
+ $stripTrailing->setAccessible(true);
+ $stripTrailing->invokeArgs(null, [&$list]);
+ $this->assertEquals(0, $list->count());
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Generic\Literals::build
+ */
+ public function testBuild(): void
+ {
+ $this->assertEquals('', $this->nodeLiterals->build());
+ //
+ $nodeLit = new Literal("|", 42);
+ $nodeLit->add(new Scalar(' sometext', 2));
+ $nodeLit->add(new Scalar(' othertext', 2));
+ $this->assertTrue($nodeLit->value instanceof NodeList);
+ $this->assertTrue($nodeLit->value->offsetGet(0) instanceof Scalar);
+ $this->assertEquals("sometext\nothertext\n", $nodeLit->build());
+ // //
+ $nodeLitClipped = new Literal("|-", 42);
+ $nodeLitClipped->add(new Scalar(' sometext', 2));
+ $nodeLitClipped->add(new Scalar(' othertext', 2));
+ $this->assertTrue($nodeLitClipped->value instanceof NodeList);
+ $this->assertTrue($nodeLitClipped->value->offsetGet(0) instanceof Scalar);
+ $this->assertEquals("sometext\nothertext", $nodeLitClipped->build());
+ //
+ $nodeLitFolded = new LiteralFolded(">", 42);
+ $nodeLitFolded->add(new Scalar(' sometext', 2));
+ $nodeLitFolded->add(new Scalar(' othertext', 2));
+ $this->assertTrue($nodeLitFolded->value instanceof NodeList);
+ $this->assertTrue($nodeLitFolded->value->offsetGet(0) instanceof Scalar);
+ $this->assertEquals("sometext othertext\n", $nodeLitFolded->build());
+ // //
+ $nodeLitFoldedClipped = new LiteralFolded(">-", 42);
+ $nodeLitFoldedClipped->add(new Scalar(' sometext', 2));
+ $nodeLitFoldedClipped->add(new Scalar(' othertext', 2));
+ $this->assertTrue($nodeLitFoldedClipped->value instanceof NodeList);
+ $this->assertTrue($nodeLitFoldedClipped->value->offsetGet(0) instanceof Scalar);
+ $this->assertEquals("sometext othertext", $nodeLitFoldedClipped->build());
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Generic\Literals::getChildValue
+ */
+ public function testGetChildValue(): void
+ {
+ $getChildValue = new \ReflectionMethod($this->nodeLiterals, 'getChildValue');
+ $getChildValue->setAccessible(true);
+ $nodeQuoted = new Quoted(' "sometext"', 1);
+ $nodeScalar = new Scalar(' sometext', 1);
+ $nodeItem = new Comment(' - itemtext', 1);
+ $nodeKey = new Blank(' key: somevalue', 1);
+ //
+ $scalarResult = $getChildValue->invokeArgs($this->nodeLiterals, [$nodeScalar, 4]);
+ $this->assertEquals('sometext', $scalarResult);
+ //
+ $keyResult = $getChildValue->invokeArgs($this->nodeLiterals, [$nodeKey, 4]);
+ $this->assertEquals("", $keyResult);
+ //
+ $itemResult = $getChildValue->invokeArgs($this->nodeLiterals, [$nodeItem, 4]);
+ // $this->assertEquals("- itemtext\n", $itemResult);
+
+ $quotedResult = $getChildValue->invokeArgs($this->nodeLiterals, [$nodeQuoted, 4]);
+ $this->assertEquals("sometext", $quotedResult);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Generic\Literals::isAwaitingChild
+ */
+ public function testIsAwaitingChild(): void
+ {
+ $uselessNode = new Blank('', 1);
+ $this->assertTrue($this->nodeLiterals->isAwaitingChild($uselessNode));
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/abstract/NodeGenericTest.php b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/abstract/NodeGenericTest.php
new file mode 100644
index 0000000..37833a4
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/units/nodes/abstract/NodeGenericTest.php
@@ -0,0 +1,244 @@
+.
+ * @license https://opensource.org/licenses/MIT The MIT license.
+ * @link https://github.com/dallgoot/yaml
+ * @since File available since Release 1.0.0
+ *
+ * @covers \Dallgoot\Yaml\Nodes\Generic\NodeGeneric
+ */
+class NodeGenericTest extends TestCase
+{
+ /**
+ * @var NodeGeneric $node An instance of "NodeGeneric" to test.
+ */
+ private $node;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp(): void
+ {
+ /** @todo Maybe check arguments of this constructor. */
+ $this->node = $this->getMockBuilder(NodeGeneric::class)
+ ->setConstructorArgs(["a string to test", 1])
+ ->getMockForAbstractClass();
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Generic\NodeGeneric::__construct
+ */
+ public function testConstruct(): void
+ {
+ $rawValue = ' somestring';
+ $lineNumber = 45;
+ $this->node->__construct($rawValue, $lineNumber);
+ $this->assertEquals($rawValue, $this->node->raw);
+ $this->assertEquals($lineNumber, $this->node->line);
+ $this->assertEquals(4, $this->node->indent);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Generic\NodeGeneric::setParent
+ */
+ public function testSetParent(): void
+ {
+ $nodeRoot = new Root();
+ $reflector = new \ReflectionClass($this->node);
+ $method = $reflector->getMethod('setParent');
+ $property = $reflector->getProperty('_parent');
+ $method->setAccessible(true);
+ $property->setAccessible(true);
+
+ $result = $method->invoke($this->node, $nodeRoot);
+ $this->assertTrue($result instanceof NodeGeneric);
+ $this->assertTrue($property->getValue($this->node) instanceof Root);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Generic\NodeGeneric::getParent
+ */
+ public function testGetParent(): void
+ {
+ //direct parent : $indent = null
+ $nodeRoot = new Root();
+ $nodeRoot->add($this->node);
+ $this->assertTrue($this->node->getParent() instanceof Root, 'parent is not a NodeRoot');
+ //undirect parent : $indent = 2
+ $nodeRoot = new Root();
+ $nodeKey = new Key(' sequence:', 1);
+ $nodeItem = new Item(' -', 2);
+ $nodeKeyInside = new Key(' keyinitem: value', 3);
+ $nodeKeyInside->add($this->node);
+ $nodeItem->add($nodeKeyInside);
+ $nodeKey->add($nodeItem);
+ $nodeRoot->add($nodeKey);
+ $this->assertEquals($nodeKey, $this->node->getParent(4));
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Generic\NodeGeneric::getParent
+ */
+ public function testGetParentException(): void
+ {
+ $this->expectException(\Exception::class);
+ $this->node->getParent();
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Generic\NodeGeneric::getRoot
+ */
+ public function testGetRoot(): void
+ {
+ $nodeRoot = new Root();
+ $nodeKey = new Key(' sequence:', 1);
+ $nodeItem = new Item(' -', 2);
+ $nodeKeyInside = new Key(' keyinitem: value', 3);
+ $nodeKeyInside->add($this->node);
+ $nodeItem->add($nodeKeyInside);
+ $nodeKey->add($nodeItem);
+ $nodeRoot->add($nodeKey);
+ $getRoot = new \ReflectionMethod($this->node, 'getRoot');
+ $getRoot->setAccessible(true);
+ $this->assertEquals($nodeRoot, $getRoot->invoke($this->node));
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Generic\NodeGeneric::getRoot
+ */
+ public function testGetRootException(): void
+ {
+ $this->expectException(\Exception::class);
+ $method = new \ReflectionMethod($this->node, 'getRoot');
+ $method->setAccessible(true);
+ $method->invoke($this->node, null);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Generic\NodeGeneric::add
+ */
+ public function testAdd(): void
+ {
+ // value is empty
+ $this->assertEquals(null, $this->node->value);
+ // add one Node
+ $blankNode = new Blank('', 1);
+ $addResult = $this->node->add($blankNode);
+ $this->assertEquals($blankNode, $addResult);
+ $this->assertEquals($blankNode, $this->node->value);
+ //value is already a NodeGeneric : add one
+ $addResult2 = $this->node->add($blankNode);
+ $this->assertEquals($blankNode, $addResult2);
+ // should change to NodeList
+ $this->assertTrue($this->node->value instanceof NodeList);
+ //and theres 2 children
+ $this->assertEquals(2, $this->node->value->count());
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Generic\NodeGeneric::getDeepestNode
+ */
+ public function testGetDeepestNode(): void
+ {
+ $child = NodeFactory::get(' key: &anchor |', 1);
+ $this->node->add($child);
+ $this->assertTrue($child->getDeepestNode() instanceof Literal);
+ $this->assertTrue($this->node->getDeepestNode() instanceof Literal);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Generic\NodeGeneric::specialProcess
+ * @todo : test call for ALL NODETYPES using folder "types" listing and object creation
+ */
+ public function testSpecialProcess(): void
+ {
+ $previous = new Blank('', 1);
+ $blankBuffer = [];
+ $this->assertFalse($this->node->specialProcess($previous, $blankBuffer));
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Generic\NodeGeneric::getTargetOnEqualIndent
+ */
+ public function testGetTargetOnEqualIndent(): void
+ {
+ $blankNode = new Blank('', 1);
+ $nodeRoot = new Root();
+ $nodeRoot->add($this->node);
+ $this->assertEquals($nodeRoot, $this->node->getTargetOnEqualIndent($blankNode));
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Generic\NodeGeneric::getTargetOnLessIndent
+ *
+ * @todo test with more content before this one
+ */
+ public function testGetTargetOnLessIndent(): void
+ {
+ $nodeRoot = new Root();
+ $keyNode = new Key('sequence:', 1);
+ $itemNode1 = new Item(' - item1', 2);
+ $itemNode2 = new Item(' - item2', 3);
+ $nodeRoot->add($keyNode);
+ $keyNode->add($itemNode1);
+ $this->assertEquals($keyNode, $itemNode1->getTargetOnLessIndent($itemNode2));
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Generic\NodeGeneric::getTargetOnMoreIndent
+ */
+ public function testGetTargetOnMoreIndent(): void
+ {
+ $previousNode = new Key('emptykey:',1);
+ $nextNode = new Item(' - itemvalue',2);
+ $this->assertEquals($previousNode, $previousNode->getTargetOnMoreIndent($nextNode));
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Generic\NodeGeneric::isAwaitingChild
+ */
+ public function testIsAwaitingChild(): void
+ {
+ $isAwaitingChild = new \ReflectionMethod($this->node, 'isAwaitingChild');
+ $isAwaitingChild->setAccessible(true);
+ $this->assertFalse($isAwaitingChild->invoke($this->node, new Blank('', 1)));
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Generic\NodeGeneric::isOneOf
+ */
+ public function testIsOneOf(): void
+ {
+ $rootNode = new Root;
+ $this->assertTrue($rootNode->isOneOf('Root'));
+ $this->assertFalse($rootNode->isOneOf('Key'));
+ $this->assertFalse($rootNode->isOneOf('Blank'));
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Nodes\Generic\NodeGeneric::__debugInfo
+ */
+ public function testDebugInfo(): void
+ {
+ $this->assertTrue(is_array($this->node->__debugInfo()));
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/units/tag/TagFactoryTest.php b/classes/vendor/81x/dallgoot/yaml/tests/units/tag/TagFactoryTest.php
new file mode 100644
index 0000000..5f9fc14
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/units/tag/TagFactoryTest.php
@@ -0,0 +1,80 @@
+.
+ * @license https://opensource.org/licenses/MIT The MIT license.
+ * @link https://github.com/dallgoot/yaml
+ * @since File available since Release 1.0.0
+ *
+ * @covers \Dallgoot\Yaml\Tag\TagFactory
+ */
+class TagFactoryTest extends TestCase
+{
+ /**
+ * @var TagFactory $tagFactory An instance of "TagFactory" to test.
+ */
+ private $tagFactory;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp(): void
+ {
+ /** @todo Maybe add some arguments to this constructor */
+ $this->tagFactory = new TagFactory();
+ }
+
+ public function testCreateCoreSchema()
+ {
+ $this->tagFactory::$schemas = [];
+ $this->tagFactory::$schemaHandles = [];
+ $createCoreSchema = new \ReflectionMethod($this->tagFactory, 'createCoreSchema');
+ $createCoreSchema->setAccessible(true);
+ $createCoreSchema->invoke(null);
+ $this->assertArrayHasKey('!!', $this->tagFactory::$schemaHandles);
+ $this->assertArrayHasKey(CoreSchema::SCHEMA_URI, $this->tagFactory::$schemas);
+ $this->assertTrue($this->tagFactory::$schemas[CoreSchema::SCHEMA_URI] instanceof CoreSchema);
+ }
+
+ public function testRegisterSchema()
+ {
+ $coreSchema = new CoreSchema;
+ $this->tagFactory::registerSchema($coreSchema::SCHEMA_URI, $coreSchema);
+ $this->assertArrayHasKey(CoreSchema::SCHEMA_URI, $this->tagFactory::$schemas);
+ }
+
+ public function testRegisterHandle()
+ {
+ $coreSchema = new CoreSchema;
+ $this->tagFactory::registerHandle("!dummy!", $coreSchema::SCHEMA_URI);
+ $this->assertArrayHasKey('!dummy!', $this->tagFactory::$schemaHandles);
+ }
+
+ public function testTransform()
+ {
+ $scalarNode = new Scalar('somestring', 1);
+ $transformed = $this->tagFactory::transform('!!str', $scalarNode);
+ $this->assertEquals('somestring', $transformed);
+ }
+
+ public function testRunHandler()
+ {
+ $scalarNode = new Scalar('somestring', 1);
+ $tagged = $this->tagFactory::runHandler('!!', 'str', $scalarNode);
+ $this->assertEquals('somestring', $tagged);
+ }
+
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/units/types/CompactTest.php b/classes/vendor/81x/dallgoot/yaml/tests/units/types/CompactTest.php
new file mode 100644
index 0000000..46799c5
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/units/types/CompactTest.php
@@ -0,0 +1,52 @@
+.
+ * @license https://opensource.org/licenses/MIT The MIT license.
+ * @link https://github.com/dallgoot/yaml
+ * @since File available since Release 1.0.0
+ *
+ * @covers \Dallgoot\Yaml\Types\Compact
+ */
+class CompactTest extends TestCase
+{
+ /**
+ * @var Compact $compact An instance of "Compact" to test.
+ */
+ private $compact;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp(): void
+ {
+ $this->compact = new Compact([]);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Types\Compact::__construct
+ */
+ public function testConstruct(): void
+ {
+ $this->compact[12] = 'abc';
+ $this->assertEquals('abc', $this->compact[12]);
+ $this->compact->prop = '123';
+ $this->assertEquals('123', $this->compact->prop);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Types\Compact::jsonSerialize
+ */
+ public function testJsonSerialize(): void
+ {
+ $this->assertTrue(is_array($this->compact->jsonSerialize()));
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/units/types/TaggedTest.php b/classes/vendor/81x/dallgoot/yaml/tests/units/types/TaggedTest.php
new file mode 100644
index 0000000..ecbbcac
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/units/types/TaggedTest.php
@@ -0,0 +1,51 @@
+.
+ * @license https://opensource.org/licenses/MIT The MIT license.
+ * @link https://github.com/dallgoot/yaml
+ * @since File available since Release 1.0.0
+ *
+ * @covers \Dallgoot\Yaml\Types\Tagged
+ */
+class TaggedTest extends TestCase
+{
+ /**
+ * @var Tagged $tag An instance of "Tagged" to test.
+ */
+ private $tag;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp(): void
+ {
+ $this->tag = new Tagged("tagName", "a string to test");
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Types\Tagged::__construct
+ */
+ public function testConstruct(): void
+ {
+ $this->assertEquals("tagName",$this->tag->tagName);
+ $this->assertEquals("a string to test",$this->tag->value);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Types\Tagged::__construct
+ */
+ public function testConstructEmptyName(): void
+ {
+ $this->expectException(\Exception::class);
+ $this->tag = new Tagged("", "a string to test");
+ }
+}
diff --git a/classes/vendor/81x/dallgoot/yaml/tests/units/types/YamlObjectTest.php b/classes/vendor/81x/dallgoot/yaml/tests/units/types/YamlObjectTest.php
new file mode 100644
index 0000000..cbc10ee
--- /dev/null
+++ b/classes/vendor/81x/dallgoot/yaml/tests/units/types/YamlObjectTest.php
@@ -0,0 +1,180 @@
+.
+ * @license https://opensource.org/licenses/MIT The MIT license.
+ * @link https://github.com/dallgoot/yaml
+ * @since File available since Release 1.0.0
+ *
+ * @covers \Dallgoot\Yaml\Types\YamlObject
+ */
+class YamlObjectTest extends TestCase
+{
+ /**
+ * @var YamlObject $yamlObject An instance of "YamlObject" to test.
+ */
+ private $yamlObject;
+
+ private $refValue = 123;
+ private $commentValue = '# this a full line comment';
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp(): void
+ {
+ /** @todo Maybe add some arguments to this constructor */
+ $this->yamlObject = new YamlObject(0);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Types\YamlObject::__construct
+ */
+ public function testConstruct(): void
+ {
+ $reflector = new \ReflectionClass($this->yamlObject);
+ $__yaml__object__api = $reflector->getProperty('__yaml__object__api');
+ $__yaml__object__api->setAccessible(true);
+ $this->yamlObject->__construct(0);
+ $this->assertTrue($__yaml__object__api->getValue($this->yamlObject) instanceof YamlProperties);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Types\YamlObject::__toString
+ */
+ public function testToString(): void
+ {
+ $this->yamlObject->setText('some text value');
+ $this->assertTrue(is_string(''.$this->yamlObject));
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Types\YamlObject::jsonSerialize
+ */
+ public function testJsonSerialize(): void
+ {
+ $this->assertEquals("_Empty YamlObject_", $this->yamlObject->jsonSerialize());
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Types\YamlObject::addReference
+ */
+ public function testAddReference(): void
+ {
+ $this->assertEquals($this->yamlObject->getAllReferences(), []);
+ $this->yamlObject->addReference('referenceName', $this->refValue);
+ $this->assertEquals($this->yamlObject->getReference('referenceName'), $this->refValue);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Types\YamlObject::getReference
+ */
+ public function testGetReference(): void
+ {
+ $this->assertEquals($this->yamlObject->getAllReferences(), []);
+ $this->yamlObject->addReference('referenceName', $this->refValue);
+ $this->assertEquals($this->yamlObject->getReference('referenceName'), $this->refValue);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Types\YamlObject::getAllReferences
+ */
+ public function testGetAllReferences(): void
+ {
+ $this->assertEquals($this->yamlObject->getAllReferences(), []);
+ $this->yamlObject->addReference('referenceName', $this->refValue);
+ $this->assertEquals($this->yamlObject->getAllReferences(), ['referenceName' => $this->refValue]);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Types\YamlObject::addComment
+ */
+ public function testAddComment(): void
+ {
+ $this->assertEquals($this->yamlObject->getComment(), []);
+ $this->yamlObject->addComment(20, $this->commentValue);
+ $this->assertEquals($this->yamlObject->getComment(20), $this->commentValue);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Types\YamlObject::getComment
+ * @depends testAddComment
+ */
+ public function testGetComment(): void
+ {
+ $this->assertEquals($this->yamlObject->getComment(), []);
+ $this->yamlObject->addComment(20, $this->commentValue);
+ $this->assertEquals($this->yamlObject->getComment(20), $this->commentValue);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Types\YamlObject::setText
+ */
+ public function testSetText(): void
+ {
+ $container = new ReflectionProperty($this->yamlObject, '__yaml__object__api');
+ $container->setAccessible(true);
+ $this->assertTrue(is_null($container->getValue($this->yamlObject)->value));
+ $txt = ' a text with leading spaces';
+ $yamlObject = $this->yamlObject->setText($txt);
+ $this->assertTrue($container->getValue($this->yamlObject)->value === ltrim($txt));
+ $this->assertTrue($yamlObject instanceof YamlObject);
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Types\YamlObject::addTag
+ */
+ public function testAddTag(): void
+ {
+ $this->assertFalse($this->yamlObject->isTagged());
+ $this->yamlObject->addTag('!', 'tag:clarkevans.com,2002');
+ $this->assertTrue($this->yamlObject->isTagged());
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Types\YamlObject::hasDocStart
+ */
+ public function testHasDocStart(): void
+ {
+ $this->assertFalse($this->yamlObject->hasDocStart());
+ $this->yamlObject->setDocStart(false);
+ $this->assertTrue($this->yamlObject->hasDocStart());
+ $this->yamlObject->setDocStart(true);
+ $this->assertTrue($this->yamlObject->hasDocStart());
+ $this->yamlObject->setDocStart(null);
+ $this->assertFalse($this->yamlObject->hasDocStart());
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Types\YamlObject::setDocStart
+ */
+ public function testSetDocStart(): void
+ {
+ $this->assertFalse($this->yamlObject->hasDocStart());
+ $this->yamlObject->setDocStart(false);
+ $this->assertTrue($this->yamlObject->hasDocStart());
+ $this->yamlObject->setDocStart(true);
+ $this->assertTrue($this->yamlObject->hasDocStart());
+ $this->yamlObject->setDocStart(null);
+ $this->assertFalse($this->yamlObject->hasDocStart());
+ }
+
+ /**
+ * @covers \Dallgoot\Yaml\Types\YamlObject::isTagged
+ */
+ public function testIsTagged(): void
+ {
+ $this->assertFalse($this->yamlObject->isTagged());
+ $this->yamlObject->addTag('!', 'tag:clarkevans.com,2002');
+ $this->assertTrue($this->yamlObject->isTagged());
+ }
+}
diff --git a/classes/vendor/81x/goat1000/svggraph/.gitattributes b/classes/vendor/81x/goat1000/svggraph/.gitattributes
new file mode 100644
index 0000000..bdb0cab
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/.gitattributes
@@ -0,0 +1,17 @@
+# Auto detect text files and perform LF normalization
+* text=auto
+
+# Custom for Visual Studio
+*.cs diff=csharp
+
+# Standard to msysgit
+*.doc diff=astextplain
+*.DOC diff=astextplain
+*.docx diff=astextplain
+*.DOCX diff=astextplain
+*.dot diff=astextplain
+*.DOT diff=astextplain
+*.pdf diff=astextplain
+*.PDF diff=astextplain
+*.rtf diff=astextplain
+*.RTF diff=astextplain
diff --git a/classes/vendor/81x/goat1000/svggraph/Algebraic.php b/classes/vendor/81x/goat1000/svggraph/Algebraic.php
new file mode 100644
index 0000000..e8ac02a
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/Algebraic.php
@@ -0,0 +1,106 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * Class for algebraic functions
+ */
+class Algebraic {
+
+ private $type = 'straight';
+ private $coeffs = [0, 1];
+
+ public function __construct($type)
+ {
+ $this->type = $type;
+ }
+
+ /**
+ * Sets the coefficients in order, lowest power first
+ */
+ public function setCoefficients(array $coefficients)
+ {
+ $this->coeffs = $coefficients;
+ }
+
+ /**
+ * Returns the y value for a + bx + cx^2 ...
+ */
+ public function __invoke($x)
+ {
+ $val = 0;
+ foreach($this->coeffs as $p => $c) {
+ switch($p) {
+ case 0: $val = bcadd($val, $c);
+ break;
+ case 1: $val = bcadd($val, bcmul($c, $x));
+ break;
+ default:
+ $val = bcadd($val, bcmul($c, bcpow($x, $p)));
+ break;
+ }
+ }
+ return $val;
+ }
+
+ /**
+ * Creates a row of the vandermonde matrix
+ */
+ public function vandermonde($x)
+ {
+ $t = $this->type;
+ return $this->{$t}($x);
+ }
+
+ private function straight($x)
+ {
+ return [$x];
+ }
+
+ private function quadratic($x)
+ {
+ return [$x, bcmul($x, $x)];
+ }
+
+ private function cubic($x)
+ {
+ $res = [$x, bcmul($x, $x)];
+ $res[] = bcmul($res[1], $x);
+ return $res;
+ }
+
+ private function quartic($x)
+ {
+ $res = $this->cubic($x);
+ $res[] = bcmul($res[1], $res[1]);
+ return $res;
+ }
+
+ private function quintic($x)
+ {
+ $res = $this->cubic($x);
+ $res[] = bcmul($res[1], $res[1]);
+ $res[] = bcmul($res[1], $res[2]);
+ return $res;
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/ArrayGraph.php b/classes/vendor/81x/goat1000/svggraph/ArrayGraph.php
new file mode 100644
index 0000000..a8c5ca2
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/ArrayGraph.php
@@ -0,0 +1,224 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class ArrayGraph extends Graph {
+
+ use MultiGraphTrait {
+ values as mgValues;
+ }
+
+ protected $inner_options;
+ protected $inner_types;
+ protected $raw_data;
+ protected $raw_settings;
+
+ public function __construct($w, $h, array $settings, array $fixed_settings = [])
+ {
+ $this->raw_settings = $settings;
+ parent::__construct($w, $h, $settings, $fixed_settings);
+ }
+
+ /**
+ * Stores raw values to make it easier to set up subgraphs
+ */
+ public function values($values)
+ {
+ $this->raw_data = $values;
+ return $this->mgValues($values);
+ }
+
+ /**
+ * Now create the subgraphs
+ */
+ public function checkValues()
+ {
+ parent::checkValues();
+ $opt = [
+ 'keep_colour_order' => true,
+ 'back_stroke_width' => 0,
+ 'back_colour' => 'none',
+ 'back_shadow' => 0,
+ 'title' => null,
+ ];
+ $opt = array_merge($this->raw_settings, $opt);
+
+ $graphs = $this->getLayout();
+ foreach($graphs as $graph) {
+ $s = new Subgraph($graph['type'], $graph['x'], $graph['y'],
+ $graph['w'], $graph['h'], array_merge($opt, $graph['options']));
+ $s->values($this->raw_data);
+ $s->setColours($this->colours);
+ $this->subgraphs[] = $s;
+ }
+ }
+
+ public function draw()
+ {
+ // all drawing is done by subgraphs
+ return $this->underShapes() . $this->overShapes();
+ }
+
+ /**
+ * Returns the list of graphs to be drawn and where to draw them
+ */
+ private function getLayout()
+ {
+ // find which datasets are going in each graph
+ $enabled_datasets = $this->multi_graph->getEnabledDatasets();
+ $graph_datasets = $this->getOption('array_graph_dataset');
+ if($graph_datasets === null) {
+
+ // default is one dataset per graph
+ $graph_datasets = [];
+ foreach($enabled_datasets as $d) {
+ $graph_datasets[] = [$d];
+ }
+ } else {
+
+ // need to check each of the selected datasets is enabled
+ $new_outer = [];
+ foreach($graph_datasets as $gd) {
+ $new_inner = [];
+ if(is_array($gd)) {
+ foreach($gd as $d) {
+ if(!in_array($d, $enabled_datasets))
+ continue;
+ $new_inner[] = $d;
+ }
+ } else {
+ if(!in_array($gd, $enabled_datasets))
+ continue;
+ $new_inner[] = $gd;
+ }
+
+ if(count($new_inner) > 0)
+ $new_outer[] = $new_inner;
+ }
+ $graph_datasets = $new_outer;
+ }
+
+ $graph_count = count($graph_datasets);
+ $cols = $this->getOption('array_graph_columns');
+ if($cols === null || $cols === 'auto') {
+ $cols = $this->calcCols($graph_count);
+ } else {
+ $cols = (int)$cols;
+ if($cols < 1)
+ throw new \Exception('Invalid array_graph_columns value: ' . $cols);
+ if($this->width / $cols < 20)
+ throw new \Exception('Option array_graph_columns too large: ' . $cols);
+ }
+ $rows = ceil($graph_count / $cols);
+ $w = $this->width / $cols;
+ $h = $this->height / $rows;
+
+ $inner_options = $this->getOption('array_graph_options');
+ if($inner_options !== null) {
+ if(!is_array($inner_options))
+ throw new \Exception('Option array_graph_options is not an array');
+
+ // check if it is an array of arrays
+ if(!isset($inner_options[0]) || !is_array($inner_options[0])) {
+
+ // put single array into outer array
+ $inner_options = [ $inner_options ];
+ }
+ } else {
+
+ // no special options
+ $inner_options = [ [ ] ];
+ }
+ $o_count = count($inner_options);
+
+ $index = 0;
+ $layout = [];
+ $last_row_count = $graph_count % $cols;
+ $last_row_offset = 0;
+ $align = $this->getOption('array_graph_align');
+ if($last_row_count && $align != 'left') {
+ $mult = $align == 'right' ? 1.0 : 0.5;
+ $last_row_offset = ($this->width - $last_row_count * $w) * $mult;
+ }
+
+ foreach($graph_datasets as $d) {
+ $col = $index % $cols;
+ $row = floor($index / $cols);
+ $x_offset = ($row == $rows - 1 ? $x_offset = $last_row_offset : 0);
+
+ $options = $inner_options[$index % $o_count];
+ $options['dataset'] = $d;
+
+ $g = [
+ 'x' => $x_offset + $w * $col,
+ 'y' => $h * $row,
+ 'w' => $w,
+ 'h' => $h,
+ 'type' => $this->getOption(['array_graph_type', $index], ['@', 'PieGraph']),
+ 'options' => $options,
+ ];
+
+ $layout[] = $g;
+ ++$index;
+ }
+
+ return $layout;
+ }
+
+ /**
+ * Calculates the best number of columns
+ */
+ private function calcCols($count)
+ {
+ if($count === 1)
+ return 1;
+
+ $w = $this->width;
+ $h = $this->height;
+ if($count === 2)
+ return $w > $h ? 2 : 1;
+
+ $matches = [];
+ for($c = 1; $c <= $count; ++$c) {
+ $r = ceil($count / $c);
+ $bw = $w / $c;
+ $bh = $h / $r;
+ $area = $bw * $bh;
+ $aspect = $bw < $bh ? $bw / $h : $bh / $bw;
+ $score = $area * ($aspect + 1);
+
+ $matches[] = [
+ 'c' => $c,
+ 'r' => $r,
+ 'area' => $area,
+ 'aspect' => $aspect,
+ 'score' => $score,
+ ];
+ }
+
+ usort($matches, function($a, $b) { return $a['score'] - $b['score']; });
+ $winner = array_pop($matches);
+ return $winner['c'];
+ }
+
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/Arrow.php b/classes/vendor/81x/goat1000/svggraph/Arrow.php
new file mode 100644
index 0000000..c72c6d2
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/Arrow.php
@@ -0,0 +1,104 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * A class for drawing arrows
+ */
+class Arrow {
+
+ protected $a;
+ protected $b;
+ protected $head_size = 7;
+ protected $head_colour = '#000';
+
+ public function __construct(Point $a, Point $b)
+ {
+ $this->a = $a;
+ $this->b = $b;
+ }
+
+ /**
+ * Sets the arrow head size (min 2 pixels)
+ */
+ public function setHeadSize($size)
+ {
+ $this->head_size = max(2, $size);
+ }
+
+ public function setHeadColour($colour)
+ {
+ $this->head_colour = $colour;
+ }
+
+ /**
+ * Returns the arrow head as the ID of a element
+ */
+ protected function getArrowHead($graph)
+ {
+ $sz = new Number($this->head_size);
+ $point = 75; // sharpness of arrow
+ $marker = [
+ 'viewBox' => "0 0 {$point} 100",
+ 'markerWidth' => $sz,
+ 'markerHeight' => $sz,
+ 'refX' => $point,
+ 'refY' => 50,
+ 'orient' => 'auto',
+ ];
+ $pd = new PathData('M', 0, 0, 'L', $point, 50, 'L', 0, 100, 'z');
+ $path = [
+ 'd' => $pd,
+ 'stroke' => $this->head_colour,
+ 'fill' => $this->head_colour,
+ ];
+ $marker_content = $graph->element('path', $path);
+ return $graph->defs->addElement('marker', $marker, $marker_content);
+ }
+
+ /**
+ * Returns the PathData for an arrow line
+ */
+ protected function getArrowPath()
+ {
+ return new PathData('M', $this->a, $this->b);
+ }
+
+ /**
+ * Returns the arrow element
+ */
+ public function draw($graph, $style = null)
+ {
+ $head_id = $this->getArrowHead($graph);
+ $p = $this->getArrowPath();
+
+ $path = [
+ 'd' => $p,
+ 'marker-end' => 'url(#' . $head_id . ')',
+ 'fill' => 'none',
+ ];
+ if(is_array($style))
+ $path = array_merge($style, $path);
+ return $graph->element('path', $path);
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/Attribute.php b/classes/vendor/81x/goat1000/svggraph/Attribute.php
new file mode 100644
index 0000000..f291baa
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/Attribute.php
@@ -0,0 +1,66 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * Data class for SVG attribute
+ */
+class Attribute {
+ public $name;
+ public $value;
+ public $encoding;
+ public $units = '';
+
+ // these properties require units to work well
+ private static $require_units = [
+ 'baseline-shift' => 1,
+ 'font-size' => 1,
+ 'kerning' => 1,
+ 'letter-spacing' => 1,
+ 'stroke-dashoffset' => 1,
+ 'stroke-width' => 1,
+ 'word-spacing' => 1,
+ ];
+
+ public function __construct($name, $value, $encoding)
+ {
+ $this->name = $name;
+ if(isset(Attribute::$require_units[$name]))
+ $this->units = 'px';
+ $this->value = $value;
+ $this->encoding = $encoding;
+
+ if(is_numeric($value))
+ $this->value = new Number($value, $this->units);
+ }
+
+ public function __toString()
+ {
+ if($this->value === null)
+ return '';
+ if(is_object($this->value))
+ return (string)$this->value;
+
+ return htmlspecialchars($this->value, ENT_COMPAT, $this->encoding);
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/Average.php b/classes/vendor/81x/goat1000/svggraph/Average.php
new file mode 100644
index 0000000..24a13ee
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/Average.php
@@ -0,0 +1,115 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * Class for average lines (using guidelines)
+ */
+class Average {
+
+ private $graph;
+ private $lines = [];
+
+ public function __construct(&$graph, &$values, $datasets)
+ {
+ foreach($datasets as $d) {
+ if(!$graph->getOption(['show_average', $d]))
+ continue;
+
+ $avg = $this->calculate($values, $d);
+ if($avg === null)
+ continue;
+
+ $line = [ $avg ];
+
+ $title = $this->getTitle($graph, $avg, $d);
+ if($title !== null && strlen($title) > 0)
+ $line[] = $title;
+
+ $cg = new ColourGroup($graph, null, 0, $d, 'average_colour');
+ $line['colour'] = $cg->stroke();
+
+ $tc = $graph->getOption(['average_title_colour', $d]);
+ if(!empty($tc)) {
+ $cg = new ColourGroup($graph, null, 0, $d, 'average_title_colour');
+ $line['text_colour'] = $cg->stroke();
+ }
+
+ $line['stroke_width'] = new Number($graph->getOption(['average_stroke_width', $d], 1));
+ $line['font_size'] = Number::units($graph->getOption(['average_font_size', $d]));
+
+ $opts = ["opacity", "above", "dash", "title_align",
+ "title_angle", "title_opacity", "title_padding", "title_position",
+ "font", "font_adjust", "font_weight", "length", "length_units"];
+ foreach($opts as $opt) {
+ $g_opt = str_replace('title', 'text', $opt);
+ $line[$g_opt] = $graph->getOption(['average_' . $opt, $d]);
+ }
+
+ // prevent line from changing graph dimensions
+ $line['no_min_max'] = true;
+ $this->lines[] = $line;
+ }
+
+ $this->graph =& $graph;
+ }
+
+ /**
+ * Adds the average lines to the graph's guidelines
+ */
+ public function getGuidelines()
+ {
+ if(empty($this->lines))
+ return;
+ $guidelines = Guidelines::normalize($this->graph->getOption('guideline'));
+ $this->graph->setOption('guideline', array_merge($guidelines, $this->lines));
+ }
+
+ /**
+ * Calculates the mean average for a dataset
+ */
+ protected function calculate(&$values, $dataset)
+ {
+ $sum = 0;
+ $count = 0;
+ foreach($values[$dataset] as $p) {
+ if($p->value === null || !is_numeric($p->value))
+ continue;
+ $sum += $p->value;
+ ++$count;
+ }
+
+ return $count ? $sum / $count : null;
+ }
+
+ /**
+ * Returns the average line title
+ */
+ protected function getTitle(&$graph, $avg, $dataset)
+ {
+ $tcb = $graph->getOption(['average_title_callback', $dataset]);
+ if(is_callable($tcb))
+ return call_user_func($tcb, $dataset, $avg);
+
+ return $graph->getOption(['average_title', $dataset]);
+ }
+}
diff --git a/classes/vendor/81x/goat1000/svggraph/Axis.php b/classes/vendor/81x/goat1000/svggraph/Axis.php
new file mode 100644
index 0000000..8d57582
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/Axis.php
@@ -0,0 +1,642 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * Class for calculating axis measurements
+ */
+class Axis {
+
+ protected $length;
+ protected $max_value;
+ protected $min_value;
+ protected $unit_size;
+ protected $min_unit;
+ protected $min_space;
+ protected $fit;
+ protected $zero;
+ protected $units_before;
+ protected $units_after;
+ protected $decimal_digits;
+ protected $uneven = false;
+ protected $rounded_up = false;
+ protected $direction = 1;
+ protected $label_callback = false;
+ protected $values = false;
+ protected $tightness = 1;
+ protected $grid_spacing;
+
+ public function __construct($length, $max_val, $min_val, $min_unit, $min_space,
+ $fit, $units_before, $units_after, $decimal_digits, $label_callback, $values)
+ {
+ if($max_val <= $min_val && $min_unit == 0)
+ throw new \Exception('Zero length axis (min >= max)');
+ $this->length = $length;
+ $this->max_value = $max_val;
+ $this->min_value = $min_val;
+ $this->min_unit = $min_unit;
+ $this->min_space = $min_space;
+ $this->fit = $fit;
+ $this->units_before = $units_before;
+ $this->units_after = $units_after;
+ $this->decimal_digits = $decimal_digits;
+ $this->label_callback = $label_callback;
+ $this->values = $values;
+ }
+
+ /**
+ * Returns min for an axis based on its min and max values
+ */
+ public static function calcMinimum($min_value, $max_value, $allow_zero,
+ $prefer_zero)
+ {
+ if($allow_zero && $prefer_zero) {
+ if($min_value > 0)
+ return 0;
+ if($max_value < 0)
+ $max_value = 0;
+ }
+
+ if($min_value > 0) {
+ $mag = floor(log10($min_value));
+ if($allow_zero) {
+ $mag1 = floor(log10($max_value));
+ if($mag1 > $mag)
+ return 0;
+ }
+ $d = pow(10, $mag);
+ $min_value = floor($min_value / $d) * $d;
+ }
+ return $min_value;
+ }
+
+ /**
+ * Returns max for an axis based on its min and max values
+ */
+ public static function calcMaximum($min_value, $max_value, $allow_zero,
+ $prefer_zero)
+ {
+ if($max_value >= 0)
+ return $max_value;
+
+ // instead of duplicating code, negate values and pass to calcMinimum()
+ $neg_max = Axis::calcMinimum(-$max_value, -$min_value, $allow_zero,
+ $prefer_zero);
+ if($neg_max > 0)
+ return -$neg_max;
+ return 0;
+ }
+
+ /**
+ * Allow length adjustment
+ */
+ public function setLength($l)
+ {
+ $this->length = $l;
+ }
+
+ /**
+ * Returns the axis length
+ */
+ public function getLength()
+ {
+ return $this->length;
+ }
+
+ /**
+ * Sets the tightness option
+ */
+ public function setTightness($t)
+ {
+ $this->tightness = $t;
+ }
+
+ /**
+ * Returns a score for "niceness"
+ */
+ private function nice($n)
+ {
+ if($this->min_unit) {
+ $d = $n / $this->min_unit;
+ if($d != floor($d))
+ return 0;
+ }
+
+ // convert to string
+ $nn = new Number($n);
+ $nn->precision = 5;
+ $s = (string)$nn;
+
+ $niceness = [
+ '0.1' => 50,
+ '0.5' => 40,
+ '0.2' => 25,
+ '2.5' => 25,
+ '1.5' => 20,
+ '0.3' => 10,
+ '0.4' => 10,
+ '1' => 100,
+ '5' => 95,
+ '2' => 95,
+ '3' => 45,
+ '4' => 40,
+ '6' => 30,
+ '8' => 20,
+ '7' => 10,
+ '9' => 5,
+ '25' => 95,
+ '15' => 40,
+ '75' => 30,
+ ];
+
+ $digits = $s;
+ if(preg_match('/^([1-9]{1,2})(0*)$/', $s, $parts)) {
+ // integer with one or two non-zero digit
+ $digits = $parts[1];
+ } elseif(preg_match('/^0\.(0+)([1-9]{1,2})$/', $s, $parts)) {
+ // float with leading zeroes
+ $digits = $parts[2];
+ }
+
+ return isset($niceness[$digits]) ? $niceness[$digits] : 0;
+ }
+
+ /**
+ * Determine the axis divisions
+ */
+ private function findDivision($length, $min, &$count, &$neg_count, &$magnitude)
+ {
+ if($this->tightness && $length / $count >= $min) {
+ return;
+ }
+
+ $c = $count - 1;
+ $inc = 0;
+
+ // $max_inc is how many extra steps the axis can grow by
+ if($this->fit)
+ $max_inc = 0;
+ else
+ $max_inc = $count / ($this->tightness ? 5 : 2);
+
+ $candidates = [];
+ while($c > 1) {
+ $m = ($count + $inc) / $c;
+ $new_magnitude = $m * $magnitude;
+ $l = $length / $c;
+ $nc = $neg_count;
+
+ $accept = false;
+ $niceness = $this->nice($new_magnitude);
+ if($niceness > 0 && $l >= $min) {
+ $accept = true;
+
+ // negative values mean an extra check
+ if($nc) {
+ $accept = false;
+ $nm = $nc / $m;
+
+ if(floor($nm) === $nm) {
+ $nc = $nm;
+ $accept = true;
+ } else {
+
+ // negative section doesn't divide cleanly, try adding from $inc
+ if($inc) {
+ for($i = 1; $i <= $inc; ++$i) {
+ $cc = $nc + $i;
+ $nm = ($nc + $i) / $m;
+
+ if(floor($nm) === $nm) {
+ $nc = $nm;
+ $accept = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if($accept) {
+ $pos = ($c - $nc) * $new_magnitude;
+ $neg = $nc * $new_magnitude;
+ $pos_niceness = $this->nice($pos);
+ $neg_niceness = $this->nice($neg);
+
+ if($this->tightness || $neg_niceness || $pos_niceness) {
+ // this division is acceptable, cost and store it
+ $cost = $m;
+ if($this->tightness) {
+ $cost += $inc * 1.5;
+ } else {
+ // increasing the length is not as costly
+ $cost += $inc * 0.5;
+
+ // reduce cost for nicer divisions
+ $cost -= $niceness / 50;
+
+ // adjust cost for axis ends
+ if($nc) {
+ if($neg_niceness) {
+ $cost -= $neg_niceness / 100;
+ if($pos_niceness)
+ $cost -= $pos_niceness / 100;
+ } else {
+ // poor choice
+ $cost += 3;
+ }
+ } elseif($pos_niceness) {
+ $cost -= $pos_niceness / 100;
+ }
+ }
+
+ $candidate = [
+ // usort requires ints to work properly
+ 'cost' => intval(1e5 * $cost),
+ 'magnitude' => $new_magnitude,
+ 'count' => $c,
+ 'neg_count' => $nc,
+
+ // these are only used for tuning / debugging
+ 'm' => $m,
+ 'real_cost' => $cost,
+ 'max_pos' => $pos,
+ 'max_neg' => $neg,
+ 'nice_mag' => $niceness,
+ 'nice_pos' => $pos_niceness,
+ 'nice_neg' => $neg_niceness,
+ ];
+
+ $candidates[] = $candidate;
+ }
+ }
+
+ if($inc < $max_inc) {
+ // increase the number of base divisions
+ ++$inc;
+ continue;
+ }
+
+ --$c;
+ $inc = 0;
+ }
+
+ if(empty($candidates))
+ return;
+
+ usort($candidates, function($a, $b) { return $a['cost'] - $b['cost']; });
+ $winner = $candidates[0];
+ $magnitude = $winner['magnitude'];
+ $count = $winner['count'];
+ $neg_count = $winner['neg_count'];
+ }
+
+ /**
+ * Sets the bar style (which means an extra unit)
+ */
+ public function bar()
+ {
+ if(!$this->rounded_up) {
+ $this->max_value += $this->min_unit;
+ $this->rounded_up = true;
+ }
+ }
+
+ /**
+ * Sets the direction of axis points
+ */
+ public function reverse()
+ {
+ $this->direction = -1;
+ }
+
+ /**
+ * Returns TRUE if the axis is reversed
+ */
+ public function reversed()
+ {
+ return $this->direction < 0;
+ }
+
+ /**
+ * Returns the grid spacing
+ */
+ protected function grid()
+ {
+ $min_space = $this->min_space;
+ $this->uneven = false;
+ $negative = $this->min_value < 0;
+ $min_sub = max($min_space, $this->length / 200);
+
+ if($this->min_value == $this->max_value)
+ $this->max_value += $this->min_unit;
+ $scale = $this->max_value - $this->min_value;
+
+ $abs_min = abs($this->min_value);
+ $magnitude = max(pow(10, floor(log10($scale))), $this->min_unit);
+ if($this->min_value > 0 || $this->fit) {
+ $count = ceil($scale / $magnitude);
+ } else {
+ $count = ceil($this->max_value / $magnitude) -
+ floor($this->min_value / $magnitude);
+ }
+
+ if($count <= 5 && $magnitude > $this->min_unit) {
+ $magnitude *= 0.1;
+ $count = ceil($this->max_value / $magnitude) -
+ floor($this->min_value / $magnitude);
+ }
+
+ $neg_count = $this->min_value < 0 ? ceil($abs_min / $magnitude) : 0;
+ $this->findDivision($this->length, $min_sub, $count, $neg_count,
+ $magnitude);
+ $grid = $this->length / $count;
+
+ // guard this loop in case the numbers are too awkward to fit
+ $guard = 10;
+ while($grid < $min_space && --$guard) {
+ $this->findDivision($this->length, $min_sub, $count, $neg_count,
+ $magnitude);
+ $grid = $this->length / $count;
+ }
+ if($guard == 0) {
+ // could not find a division
+ while($grid < $min_space && $count > 1) {
+ $count *= 0.5;
+ $neg_count *= 0.5;
+ $magnitude *= 2;
+ $grid = $this->length / $count;
+ $this->uneven = true;
+ }
+ }
+
+ $this->unit_size = $this->length / ($magnitude * $count);
+ $this->zero = $negative ? $neg_count * $grid :
+ -$this->min_value * $grid / $magnitude;
+
+ return $grid;
+ }
+
+ /**
+ * Returns the size of a unit in grid space
+ */
+ public function unit()
+ {
+ if(!isset($this->unit_size))
+ $this->grid();
+
+ return $this->unit_size;
+ }
+
+ /**
+ * Returns the distance along the axis where 0 should be
+ */
+ public function zero()
+ {
+ if(!isset($this->zero))
+ $this->grid();
+
+ return $this->zero;
+ }
+
+ /**
+ * Returns TRUE if the grid spacing does not fill the grid
+ */
+ public function uneven()
+ {
+ return $this->uneven;
+ }
+
+ /**
+ * Returns the distance in pixels $u takes from $pos
+ */
+ public function measureUnits($pos, $u)
+ {
+ // on an ordinary axis this works fine
+ $l = $this->position($u);
+ $zero = $this->position(0);
+ return $l - $zero;
+ }
+
+ /**
+ * Returns the position of a value on the axis
+ */
+ public function position($index, $item = null)
+ {
+ $value = $index;
+ if($item !== null && !$this->values->associativeKeys())
+ $value = $item->key;
+ if(!is_numeric($value))
+ return null;
+ return $this->zero() + ($value * $this->unit());
+ }
+
+ /**
+ * Returns the position of an associative key, if possible
+ */
+ public function positionByKey($key)
+ {
+ if($this->values && $this->values->associativeKeys()) {
+
+ // only need to look through dataset 0 because multi-dataset graphs
+ // convert to structured
+ $index = 0;
+ foreach($this->values[0] as $item) {
+ if($item->key == $key) {
+ return $this->zero() + ($index * $this->unit());
+ }
+ ++$index;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the position of the origin
+ */
+ public function origin()
+ {
+ // for a linear axis, it should be the zero point
+ return $this->zero();
+ }
+
+ /**
+ * Returns the value at a position on the axis
+ */
+ public function value($position)
+ {
+ return ($position - $this->zero()) / $this->unit();
+ }
+
+ /**
+ * Return the before units text
+ */
+ public function beforeUnits()
+ {
+ return $this->units_before;
+ }
+
+ /**
+ * Return the after units text
+ */
+ public function afterUnits()
+ {
+ return $this->units_after;
+ }
+
+ /**
+ * Returns a single GridPoint
+ */
+ protected function getGridPoint($position, $value)
+ {
+ $key = $text = $value;
+ $item = null;
+
+ if($this->values) {
+
+ // try structured data first
+ $item = $this->values->getItem($value);
+ if($item !== null && $this->values->getData($value, 'axis_text', $text))
+ return new GridPoint($position, $text, $value, $item);
+
+ // use the key if it is not the same as the value
+ $key = $this->values->getKey($value);
+ }
+
+ // if there is a callback, use it
+ if(is_callable($this->label_callback)) {
+ // assoc keys should have integer indices
+ if($this->values && $this->values->associativeKeys())
+ $value = (int)round($value);
+ $text = call_user_func($this->label_callback, $value, $key);
+ return new GridPoint($position, $text, $value, $item);
+ }
+
+ if($key !== $value)
+ return new GridPoint($position, $key, $value, $item);
+
+ $n = new Number($value, $this->units_after, $this->units_before);
+ $text = $n->format($this->decimal_digits);
+ return new GridPoint($position, $text, $value, $item);
+ }
+
+ /**
+ * Returns the grid points as an array of GridPoints
+ * if $start is NULL, just set up the grid spacing without returning points
+ */
+ public function getGridPoints($start)
+ {
+ $this->grid_spacing = $spacing = $this->grid();
+ $dlength = $this->length + $spacing * 0.5;
+ if($dlength / $spacing > 10000) {
+ $pcount = $dlength / $spacing;
+ throw new \Exception('Too many grid points (' . $this->min_value . '->' .
+ $this->max_value . ' = ' . $pcount . ' points @ ' . $spacing . 'px separation)');
+ }
+ if($start === null)
+ return;
+
+ $c = $pos = 0;
+ $points = [];
+ while($pos < $dlength) {
+ $value = ($pos - $this->zero) / $this->unit_size;
+ $position = $start + ($this->direction * $pos);
+ $points[] = $this->getGridPoint($position, $value);
+ $pos = ++$c * $spacing;
+ }
+ // uneven means the divisions don't fit exactly, so add the last one in
+ if($this->uneven) {
+ $pos = $this->length - $this->zero;
+ $value = $pos / $this->unit_size;
+ $position = $start + ($this->direction * $this->length);
+ $points[] = $this->getGridPoint($position, $value);
+ }
+
+ if($this->direction < 0) {
+ usort($points, function($a, $b) { return $b->position - $a->position; });
+ } else {
+ usort($points, function($a, $b) { return $a->position - $b->position; });
+ }
+
+ $this->grid_spacing = $spacing;
+ return $points;
+ }
+
+ /**
+ * Returns the grid subdivision points as an array
+ */
+ public function getGridSubdivisions($min_space, $min_unit, $start, $fixed)
+ {
+ if(!$this->grid_spacing)
+ throw new \Exception('grid_spacing not set');
+
+ $subdivs = [];
+ $spacing = $this->findSubdiv($this->grid_spacing, $min_space, $min_unit,
+ $fixed);
+ if(!$spacing)
+ return $subdivs;
+
+ $c = $pos1 = $pos2 = 0;
+ $pos1 = $c * $this->grid_spacing;
+ while($pos1 + $spacing < $this->length) {
+ $d = 1;
+ $pos2 = $d * $spacing;
+ while($pos2 < $this->grid_spacing) {
+ $subdivs[] = new GridPoint($start + (($pos1 + $pos2) * $this->direction), '', 0);
+ ++$d;
+ $pos2 = $d * $spacing;
+ }
+ ++$c;
+ $pos1 = $c * $this->grid_spacing;
+ }
+ return $subdivs;
+ }
+
+ /**
+ * Find the subdivision size
+ */
+ private function findSubdiv($grid_div, $min, $min_unit, $fixed)
+ {
+ if(is_numeric($fixed))
+ return $this->unit_size * $fixed;
+
+ $D = $grid_div / $this->unit_size; // D = actual division size
+ $min = max($min, $min_unit * $this->unit_size); // use the larger minimum value
+ $max_divisions = (int)floor($grid_div / $min);
+
+ // can we subdivide at all?
+ if($max_divisions <= 1)
+ return null;
+
+ // convert $D to an integer in the 100's range
+ $D1 = (int)round(100 * (pow(10,-floor(log10($D)))) * $D);
+ for($divisions = $max_divisions; $divisions > 1; --$divisions) {
+ // if $D1 / $divisions is not an integer, $divisions is no good
+ $dq = $D1 / $divisions;
+ if($dq - floor($dq) == 0)
+ return $grid_div / $divisions;
+ }
+ return null;
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/AxisDateTime.php b/classes/vendor/81x/goat1000/svggraph/AxisDateTime.php
new file mode 100644
index 0000000..dfedc5b
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/AxisDateTime.php
@@ -0,0 +1,724 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * Class for calculating date/time axis measurements
+ */
+class AxisDateTime extends Axis {
+
+ protected $grid_space;
+ protected $grid_split = 0;
+ protected $start = 0;
+ protected $end = 0;
+ protected $duration = 1;
+ protected $grid_units;
+ protected $grid_unit_count;
+ protected $label_callback;
+ protected $axis_text_format = 'Y-m-d';
+ protected $timezone = null;
+ protected $formatter = null;
+ protected $div = null;
+ protected $division = null;
+ protected $levels = null;
+
+ protected static $week_start = 'monday';
+ protected static $weekdays = [
+ 'sunday' => 0,
+ 'monday' => 1,
+ 'tuesday' => 2,
+ 'wednesday' => 3,
+ 'thursday' => 4,
+ 'friday' => 5,
+ 'saturday' => 6
+ ];
+
+ /**
+ * The list of possible divisions. Fields are:
+ * 0 - division unit
+ * 1 - number of units in duration
+ * 2 - array of division indices for subdivision
+ */
+ protected static $divisions = [
+ // the indices are numbered for clarity
+ 0 => ['second', 1],
+ 1 => ['second', 2, [0]],
+ 2 => ['second', 5, [0]],
+ 3 => ['second', 10, [0, 1, 2]],
+ 4 => ['second', 15, [0, 2]],
+ 5 => ['second', 20, [0, 1, 2, 3]],
+ 6 => ['second', 30, [0, 1, 2, 3, 4]],
+ 7 => ['minute', 1, [3, 4, 5, 6]],
+ 8 => ['minute', 2, [6, 7]],
+ 9 => ['minute', 5, [7]],
+ 10 => ['minute', 10, [7, 8, 9]],
+ 11 => ['minute', 15, [7, 9]],
+ 12 => ['minute', 20, [7, 8, 9, 10]],
+ 13 => ['minute', 30, [8, 9, 10, 11]],
+ 14 => ['hour', 1, [9, 10, 11, 12, 13]],
+ 15 => ['hour', 2, [11, 13, 14]],
+ 16 => ['hour', 3, [13, 14]],
+ 17 => ['hour', 4, [13, 14, 15]],
+ 18 => ['hour', 6, [14, 15, 16]],
+ 19 => ['hour', 8, [14, 15, 17]],
+ 20 => ['hour', 12, [14, 15, 16, 17, 18, 19]],
+ 21 => ['day', 1, [14, 18, 20]],
+ 22 => ['day', 7, [21]],
+ 23 => ['day', 14, [21, 22]],
+ 24 => ['month', 1, [21]],
+ 25 => ['month', 2, [21, 24]],
+ 26 => ['month', 3, [24]],
+ 27 => ['month', 6, [24, 25, 26]],
+ 28 => ['year', 1, [24, 25, 26, 27]],
+ 29 => ['year', 2, [27, 28]],
+ 30 => ['year', 5, [28]],
+ 31 => ['year', 10, [28, 29, 30]],
+ 32 => ['year', 20, [28, 29, 30, 31]],
+ 33 => ['year', 50, [30, 31]],
+ 34 => ['year', 100, [31, 32, 33]],
+ 35 => ['year', 500, [34]],
+ 36 => ['year', 1000, [34, 35]],
+ 37 => ['year', 10000],
+ 38 => ['year', 100000],
+ 39 => ['year', 1000000],
+ ];
+
+ /**
+ * The size of each unit in seconds
+ */
+ protected static $unit_sizes = [
+ 'second' => 1,
+ 'minute' => 60,
+ 'hour' => 3600,
+ 'day' => 86400,
+ 'month' => 2629800, // avg year / 12
+ 'year' => 31557600 // avg year = 365.25 days (ignoring leap centuries)
+ ];
+
+ /**
+ * Default format strings for each unit size
+ */
+ protected static $formats = [
+ 'second' => 'Y-m-d H:i:s',
+ 'minute' => 'Y-m-d H:i',
+ 'hour' => 'Y-m-d H:i',
+ 'day' => 'Y-m-d',
+ 'month' => 'Y-m',
+ 'year' => 'Y'
+ ];
+
+ /**
+ * Multi-level format strings
+ */
+ protected static $formats_level = [
+ 'second' => ['H:i:s', 'd', 'F', 'Y'],
+ 'minute' => ['H:i', 'd', 'F', 'Y'],
+ 'hour' => ['H:i', 'd', 'F', 'Y'],
+ 'day' => ['d', 'M', 'Y'],
+ 'month' => ['M', 'Y'],
+ 'year' => ['Y'],
+ ];
+
+ public function __construct($length, $max_val, $min_val, $min_space,
+ $fixed_division, $levels, $options)
+ {
+ if($max_val < $min_val)
+ throw new \Exception('Zero length axis (min >= max)');
+ $this->length = $length;
+ // if $min_space > $length, use $length instead
+ $this->min_space = $min_space = min($length, $min_space);
+ $this->uneven = false;
+
+ // convert actual min/max to start/end times
+ $start_date = new \DateTime('@' . $min_val);
+ $end_date = new \DateTime('@' . $max_val);
+ $this->timezone = new \DateTimeZone(date_default_timezone_get());
+ $start_date->setTimezone($this->timezone);
+ $end_date->setTimezone($this->timezone);
+ $this->formatter = new DateTimeFormatter;
+
+ // set the week start day before finding divisions
+ if(isset($options['datetime_week_start']) &&
+ isset(AxisDateTime::$weekdays[$options['datetime_week_start']]))
+ AxisDateTime::$week_start = $options['datetime_week_start'];
+
+ if(!empty($fixed_division)) {
+ list($units, $count) = AxisDateTime::parseFixedDivisions($fixed_division,
+ $min_val, $max_val, $length);
+ $start = AxisDateTime::startTime($start_date, $units, $count);
+ $end = AxisDateTime::endTime($end_date, $units, $count, $start);
+
+ $this->start = $start->format('U');
+ $this->end = $end->format('U');
+ $this->duration = ($this->end - $this->start) + 1;
+ $this->grid_units = $units;
+ $this->grid_unit_count = $count;
+
+ // set the division number (if it is a standard division)
+ $this->division = 0;
+ foreach(AxisDateTime::$divisions as $key => $div) {
+ if($div[0] == $units && $div[1] == $count)
+ $this->division = $key;
+ }
+
+ } else {
+
+ // find a sensible division
+ $div = AxisDateTime::findBestDivision($start_date, $end_date, $length,
+ $min_space);
+ $this->div = $div;
+ $this->start = $div[0]->format('U');
+ $this->end = $div[1]->format('U');
+ $this->duration = ($this->end - $this->start) + 1;
+
+ $this->division = $div[2];
+ $this->grid_units = AxisDateTime::$divisions[$this->division][0];
+ $this->grid_unit_count = AxisDateTime::$divisions[$this->division][1];
+ }
+ $this->label_callback = [$this, 'dateText'];
+
+ // get the axis text format from the options, or use defaults
+ $this->axis_text_format = AxisDateTime::$formats[$this->grid_units];
+ if(is_numeric($levels) && $levels > 1) {
+ $this->levels = (int)$levels;
+ $this->axis_text_format = AxisDateTime::$formats_level[$this->grid_units];
+ }
+
+ $text_format = null;
+ if(isset($options['datetime_text_format'])) {
+ $fmt = $options['datetime_text_format'];
+ if(is_array($fmt) && isset($fmt[$this->grid_units])) {
+ $text_format = $fmt[$this->grid_units];
+ } elseif(!empty($fmt)) {
+ $text_format = $fmt;
+ }
+ }
+
+ if($text_format !== null)
+ $this->axis_text_format = $text_format;
+ }
+
+ /**
+ * Finds the best division for the given start and end date/time
+ * @param DateTime $start
+ * @param DateTime $end
+ * @param number $length
+ * @param number $min_space
+ * @param number $subdivision
+ * Returns array($start, $end, $div_index, $div_count) or NULL if there is no
+ * subdivision possible
+ */
+ private static function findBestDivision($start, $end, $length, $min_space,
+ $subdivision = false)
+ {
+ $max_divisions = floor($length / $min_space);
+ $duration_s = $end->format('U') - $start->format('U');
+ $avg_duration = ceil($duration_s / $max_divisions);
+
+ $choice = null;
+ $divisions = 1;
+ $subdivide = false;
+ if($subdivision === false) {
+ $d_list = array_keys(AxisDateTime::$divisions);
+ } else {
+ // give up now if this can't be subdivided
+ if(!isset(AxisDateTime::$divisions[$subdivision][2]))
+ return null;
+ $d_list = AxisDateTime::$divisions[$subdivision][2];
+ $subdivide = true;
+ }
+
+ foreach($d_list as $i) {
+ $d = AxisDateTime::$divisions[$i];
+ $div_duration = $d[1] * AxisDateTime::$unit_sizes[$d[0]];
+
+ if($div_duration >= $avg_duration) {
+ $divisions = floor($duration_s / $div_duration);
+ $unit = $d[0];
+ $nunits = $d[1];
+
+ // get the updated start and end times to fit with the spacing
+ $new_start = AxisDateTime::startTime($start, $unit, $nunits);
+ $new_end = AxisDateTime::endTime($end, $unit, $nunits, $new_start);
+ $new_duration = $new_end->format('U') - $new_start->format('U');
+ $new_avg_duration = (int)ceil($new_duration / $max_divisions);
+
+ if($div_duration >= $new_avg_duration) {
+ $choice = $d;
+ break;
+ }
+ }
+ }
+ if($choice === null) {
+ if($subdivide)
+ return null;
+ throw new \Exception('Unable to find divisions for DateTime axis');
+ }
+
+ return [$new_start, $new_end, $i, $divisions];
+ }
+
+ /**
+ * Returns the start of the current $n $units of $time
+ */
+ private static function startTime($time, $unit, $n)
+ {
+ $formats = [
+ 'year' => '00:00:00 January 1',
+ 'month' => '00:00:00 first day of',
+ 'day' => '00:00:00',
+ ];
+ $datetime = clone $time;
+ if($n == 1 && isset($formats[$unit])) {
+ $datetime->modify($formats[$unit]);
+
+ } else {
+ switch($unit) {
+ case 'year':
+ $y = $time->format('Y');
+ $y -= $y % $n;
+ $datetime->setDate($y, 1, 1);
+ $datetime->setTime(0, 0);
+ break;
+
+ case 'month':
+ $y = $time->format('Y');
+ $m = $time->format('n') - 1;
+ $m -= $m % $n;
+ $datetime->setDate($y, $m + 1, 1);
+ $datetime->setTime(0, 0);
+ break;
+
+ case 'day':
+ $day = $datetime->format('w'); // 0-6, Sun-Sat
+ $dow = AxisDateTime::$weekdays[AxisDateTime::$week_start];
+
+ // always start on the right weekday
+ if($day == $dow) {
+ $datetime->modify('00:00:00');
+ } else {
+ $datetime->modify('00:00:00 last ' . AxisDateTime::$week_start);
+ }
+ break;
+
+ case 'hour':
+ $h = $datetime->format('H');
+ if($n > 1)
+ $h = $h - ($h % $n);
+ $newtime = sprintf('%02d:00:00', $h);
+ $datetime->modify($newtime);
+ break;
+
+ case 'minute':
+ $m = $datetime->format('i');
+ if($n > 1)
+ $m = $m - ($m % $n);
+ $newtime = $datetime->format(sprintf('H:%02d:00', $m));
+ $datetime->modify($newtime);
+ break;
+
+ case 'second':
+ $s = $datetime->format('s');
+ if($n > 1)
+ $s = $s - ($s % $n);
+ $newtime = $datetime->format(sprintf('H:i:%02d', $s));
+ $datetime->modify($newtime);
+ break;
+ }
+ }
+ return $datetime;
+ }
+
+ /**
+ * Returns the end of the current $n $units of $time, started at $start
+ */
+ private static function endTime($time, $unit, $n, $start)
+ {
+ $formats = [
+ 'year' => '23:59:59 December 31',
+ 'month' => '23:59:59 last day of',
+ 'day' => '23:59:59',
+ ];
+ $datetime = clone $time;
+ if($n == 1 && isset($formats[$unit])) {
+ $datetime->modify($formats[$unit]);
+
+ } else {
+ switch($unit) {
+ case 'year':
+ $y = $time->format('Y');
+ $new_y = new Number($y - ($y % $n) + $n - 1);
+ $datetime->modify($new_y . '-12-31 23:59:59');
+ break;
+
+ case 'month':
+ $datetime->modify('00:00:00 first day of');
+ $diff = $datetime->diff($start);
+ $months = ($diff->y * 12) + $diff->m;
+ $new_months = new Number($months - ($months % $n) + $n - 1);
+ $datetime = clone $start;
+ $datetime->modify('+' . $new_months . ' month 23:59:59 last day of');
+ break;
+
+ case 'day':
+ $datetime->modify('00:00:00');
+ $diff = $datetime->diff($start);
+ $days = new Number($diff->days - ($diff->days % $n) + $n - 1);
+ $datetime = clone $start;
+ $datetime->modify('+' . $days . ' day 23:59:59');
+ break;
+
+ case 'hour':
+ if($n > 1) {
+ $diff = $datetime->diff($start);
+ $hours = ($diff->days * 24) + $diff->h;
+ $hours = new Number($hours - ($hours % $n) + $n - 1);
+ $datetime = clone $start;
+ $datetime->modify('+' . $hours . ' hour 59 minute 59 second');
+ } else {
+ $h = $datetime->format('H');
+ $newtime = sprintf('%02d:59:59', $h);
+ $datetime->modify($newtime);
+ }
+ break;
+
+ case 'minute':
+ if($n > 1) {
+ $diff = $datetime->diff($start);
+ $minutes = ((($diff->days * 24) + $diff->h) * 60) + $diff->i;
+ $minutes = new Number($minutes - ($minutes % $n) + $n - 1);
+ $datetime = clone $start;
+ $datetime->modify('+' . $minutes . ' minute 59 second');
+ } else {
+ $m = $datetime->format('i');
+ $newtime = $datetime->format(sprintf('H:%02d:59', $m));
+ $datetime->modify($newtime);
+ }
+ break;
+
+ case 'second':
+ if($n > 1) {
+ $diff = $datetime->diff($start);
+ $seconds = ($diff->days * 86400) + ($diff->h * 3600) +
+ ($diff->i * 60) + $diff->s;
+ $seconds = new Number($seconds - ($seconds % $n) + $n - 1);
+ $datetime = clone $start;
+ $datetime->modify('+' . $seconds . ' second');
+ }
+ // if $n == 1, no modifications are required
+ break;
+ }
+ }
+ return $datetime;
+ }
+
+ /**
+ * Returns the distance in pixels $u takes from $pos
+ */
+ public function measureUnits($pos, $u)
+ {
+ $i = Coords::parseValue($pos);
+
+ // start with a plain date
+ $datetime = new \DateTime('@0');
+ $datetime->setTimezone($this->timezone);
+ if($i['simple']) {
+ $a = new Number($pos);
+ $datetime = new \DateTime('@' . $a);
+ $datetime->setTimezone($this->timezone);
+ } elseif($i['grid']) {
+ if($i['units']) {
+ list($units, $unit_count) = AxisDateTime::parseFixedDivisions($i['value'],
+ $this->start, $this->end, $this->length);
+ $datetime->setTimezone($this->timezone);
+ $uc = new Number($unit_count);
+ $datetime->modify('+' . $uc . ' ' . $units);
+ } else {
+ $v = Graph::dateConvert($i['value']);
+ $a = new Number($v);
+ $datetime = new \DateTime('@' . $a);
+ $datetime->setTimezone($this->timezone);
+ }
+ }
+
+ $start_value = $datetime->format('U');
+ $start_pos = $this->length * ($start_value - $this->start) / $this->duration;
+
+ list($units, $unit_count) = AxisDateTime::parseFixedDivisions($u,
+ $this->start, $this->end, $this->length);
+
+ $datetime->setTimezone($this->timezone);
+ $uc = new Number($unit_count);
+ $datetime->modify('+' . $uc . ' ' . $units);
+ $value = $datetime->format('U');
+ $end_pos = $this->length * ($value - $this->start) / $this->duration;
+ return $end_pos - $start_pos;
+ }
+
+ /**
+ * Returns the position of a value on the axis
+ */
+ public function position($index, $item = null)
+ {
+ if($item === null) {
+ $value = $index;
+
+ // support '10 hours' type of position
+ if(is_string($index) && strpos($index, ' ') !== false) {
+ list($units, $unit_count) = AxisDateTime::parseFixedDivisions($value,
+ $this->start, $this->end, $this->length);
+
+ // initialise with 0, not the current time/date
+ $datetime = new \DateTime('@0');
+ $datetime->setTimezone($this->timezone);
+ $uc = new Number($unit_count);
+ $datetime->modify('+' . $uc . ' ' . $units);
+ $value = $datetime->format('U');
+ }
+ } else {
+ $value = $item->key;
+ }
+ return $this->length * ($value - $this->start) / $this->duration;
+ }
+
+ /**
+ * Returns the position by key, which is a datetime string
+ */
+ public function positionByKey($key)
+ {
+ // ignore grid-relative positions
+ if(in_array($key, ['t', 'l', 'b', 'r', 'h', 'w', 'cx', 'cy']))
+ return null;
+ $value = Graph::dateConvert($key);
+ if($value)
+ return $this->position($value);
+ return null;
+ }
+
+ /**
+ * Returns the value at a position on the axis
+ */
+ public function value($position)
+ {
+ return $this->start + $position * $this->duration / $this->length;
+ }
+
+ /**
+ * Returns the position of the origin
+ */
+ public function origin()
+ {
+ // time started before whatever date the graph starts with
+ return 0;
+ }
+
+ /**
+ * Returns the unit size
+ */
+ public function unit()
+ {
+ $u = AxisDateTime::$unit_sizes[$this->grid_units];
+ $w = $this->length * $u / $this->duration;
+ return max(1, $w);
+ }
+
+ /**
+ * Not actually 0, but the position of the axis
+ */
+ public function zero()
+ {
+ return 0;
+ }
+
+ /**
+ * Returns the grid points as an array of GridPoints
+ */
+ public function getGridPoints($start)
+ {
+ if($start === null)
+ return;
+ $c = $pos = 0;
+ $dlength = $this->length + 1; // allow 1 pixel overflow
+
+ $units = $this->grid_units;
+ $unit_count = $this->grid_unit_count;
+ $value = $this->start;
+
+ // prevent too many grid points if something goes wrong
+ $limit = 1000;
+
+ $points = [];
+ while(floor($pos) < $dlength && ++$c < $limit) {
+
+ $position = $start + ($pos * $this->direction);
+ $points[] = $this->getGridPoint($position, $value);
+
+ $datetime = new \DateTime('@' . $this->start);
+ $datetime->setTimezone($this->timezone);
+ $offset = new Number($c * $unit_count);
+ $datetime->modify('+' . $offset . ' ' . $units);
+ $value = $datetime->format('U');
+ $pos = $this->position($value);
+ }
+
+ return $points;
+ }
+
+ /**
+ * Returns the grid subdivision points as an array
+ */
+ public function getGridSubdivisions($min_space, $min_unit, $start, $fixed)
+ {
+ $subdivs = [];
+ if(!empty($fixed)) {
+ list($units, $unit_count) = AxisDateTime::parseFixedDivisions($fixed,
+ $this->start, $this->end, $this->length);
+
+ } else {
+ // if the main division is the lowest level, there is no subdivision
+ if($this->division == 0)
+ return $subdivs;
+
+ $start_date = new \DateTime('@' . $this->start);
+ $start_date->setTimezone($this->timezone);
+ $end_date = new \DateTime('@' . $this->end);
+ $end_date->setTimezone($this->timezone);
+
+ $div = AxisDateTime::findBestDivision($start_date, $end_date,
+ $this->length, $min_space, $this->division);
+
+ // if no divisions found, stop now
+ if($div === null)
+ return $subdivs;
+ $division = $div[2];
+
+ $units = AxisDateTime::$divisions[$division][0];
+ $unit_count = AxisDateTime::$divisions[$division][1];
+ }
+ $value = $this->start;
+
+ // get the main divisions, turn them into a map of where not to put a
+ // subdivision
+ $main_divisions = $this->getGridPoints($start);
+ $not_here = [];
+ foreach($main_divisions as $d) {
+ $not_here[floor($d->position)] = $d->value;
+ }
+
+ // prevent too many grid points if something goes wrong
+ $limit = 1000;
+
+ $c = $pos = 0;
+ $dlength = $this->length + 1; // allow 1 pixel overflow
+ $text = '';
+ while(floor($pos) < $dlength && ++$c < $limit) {
+
+ $position = $start + ($pos * $this->direction);
+ if(!isset($not_here[floor($position)]) &&
+ !isset($not_here[ceil($position)]))
+ $subdivs[] = new GridPoint($position, $text, $value);
+
+ $datetime = new \DateTime('@' . $this->start);
+ $datetime->setTimezone($this->timezone);
+ $offset = new Number($c * $unit_count);
+ $datetime->modify('+' . $offset . ' ' . $units);
+ $value = $datetime->format('U');
+ $pos = $this->position($value);
+ }
+
+ return $subdivs;
+ }
+
+ /**
+ * Converts a fixed division option to a unit size and count.
+ * $start_time and $end_time are unix timestamps
+ * Returns array($unit, $count)
+ */
+ private static function parseFixedDivisions($fixed_opt, $start_time,
+ $end_time, $axis_length)
+ {
+ if(strpos($fixed_opt, ' ') !== false) {
+ // number and unit
+ list($unit_count, $units) = explode(' ', $fixed_opt);
+
+ } elseif(is_numeric($fixed_opt)) {
+ // number without units
+ $unit_count = $fixed_opt * 1;
+ // make a guess at the units to use
+ $min_interval = ($end_time - $start_time) / $axis_length;
+ foreach(AxisDateTime::$unit_sizes as $unit => $size) {
+ if($size > $min_interval)
+ break;
+ }
+ $units = $unit;
+
+ } else {
+ // unit without number
+ $unit_count = 1;
+ $units = $fixed_opt;
+ }
+
+ $units = rtrim($units, 's');
+ if(!isset(AxisDateTime::$unit_sizes[$units]))
+ throw new \Exception('Unrecognized datetime units [' . $units . ']');
+ if(!is_numeric($unit_count) || $unit_count < 1)
+ $unit_count = 1;
+
+ return [$units, $unit_count];
+ }
+
+ /**
+ * Formats the axis text
+ */
+ public function dateText($f)
+ {
+ $dt = new \DateTime('@' . $f);
+ $dt->setTimezone($this->timezone);
+
+ if(!is_array($this->axis_text_format))
+ return $this->formatter->format($dt, $this->axis_text_format);
+
+ $strings = [];
+ foreach($this->axis_text_format as $fmt)
+ $strings[] = $this->formatter->format($dt, $fmt);
+ return $strings;
+ }
+
+ /**
+ * Returns the format in use
+ */
+ public function getFormat($level = 0)
+ {
+ if(is_array($this->axis_text_format))
+ return $this->axis_text_format[$level];
+ return $this->axis_text_format;
+ }
+
+ /**
+ * Returns the formatted, localized date/time
+ */
+ public function format($dt, $fmt)
+ {
+ return $this->formatter->format($dt, $fmt);
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/AxisDoubleEnded.php b/classes/vendor/81x/goat1000/svggraph/AxisDoubleEnded.php
new file mode 100644
index 0000000..47c384a
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/AxisDoubleEnded.php
@@ -0,0 +1,97 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * Class for axis with +ve on both sides of zero
+ */
+class AxisDoubleEnded extends Axis {
+
+ /**
+ * Constructor calls Axis constructor with 0.5 * length
+ */
+ public function __construct($length, $max_val, $min_val, $min_unit, $min_space,
+ $fit, $units_before, $units_after, $decimal_digits, $label_callback)
+ {
+ if($min_val < 0)
+ throw new \Exception('Negative value for double-ended axis');
+ parent::__construct($length / 2, $max_val, $min_val, $min_unit, $min_space,
+ $fit, $units_before, $units_after, $decimal_digits, $label_callback, false);
+ }
+
+ /**
+ * Return the full axis length, not the 1/2 length
+ */
+ public function getLength()
+ {
+ return $this->length * 2;
+ }
+
+ /**
+ * Returns the distance along the axis where 0 should be
+ */
+ public function zero()
+ {
+ return $this->zero = $this->length;
+ }
+
+ /**
+ * Returns the grid points as an array of GridPoints
+ */
+ public function getGridPoints($start)
+ {
+ $points = parent::getGridPoints($start);
+ if($start === null)
+ return;
+ $new_points = [];
+ $z = $this->zero();
+ foreach($points as $p) {
+ $new_points[] = new GridPoint($p->position + $z, $p->getText(), $p->value);
+ if($p->value != 0)
+ $new_points[] = new GridPoint((2 * $start) + $z - $p->position, $p->getText(), $p->value);
+ }
+
+ if($this->direction < 0) {
+ usort($new_points, function($a, $b) { return $b->position - $a->position; });
+ } else {
+ usort($new_points, function($a, $b) { return $a->position - $b->position; });
+ }
+ return $new_points;
+ }
+
+ /**
+ * Returns the grid subdivision points as an array
+ */
+ public function getGridSubdivisions($min_space, $min_unit, $start, $fixed)
+ {
+ $divs = parent::getGridSubdivisions($min_space, $min_unit, $start, $fixed);
+ $new_divs = [];
+ $z = $this->zero();
+ foreach($divs as $d) {
+ $new_divs[] = new GridPoint($d->position + $z, '', 0);
+ $new_divs[] = new GridPoint((2 * $start) + $z - $d->position, '', 0);
+ }
+
+ return $new_divs;
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/AxisFactory.php b/classes/vendor/81x/goat1000/svggraph/AxisFactory.php
new file mode 100644
index 0000000..dba662e
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/AxisFactory.php
@@ -0,0 +1,190 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class AxisFactory {
+
+ private $datetime = false;
+ private $settings = [];
+ private $fit = true;
+ private $bar = false;
+ private $reverse = false;
+
+ /**
+ * Constructor
+ *
+ * $datetime = datetime keys (bool)
+ * $settings = settings array
+ * $fit = fit to values (bool)
+ * $bar = bar-style axis (bool)
+ * $reverse = reverse direction (bool)
+ */
+ public function __construct($datetime, &$settings, $fit = true, $bar = false,
+ $reverse = false)
+ {
+ $this->datetime = $datetime;
+ $this->settings =& $settings;
+ $this->fit = $fit;
+ $this->bar = $bar;
+ $this->reverse = $reverse;
+ }
+
+ /**
+ * Creates and returns axis
+ *
+ * $length = length of axis
+ * $min = minimum value
+ * $max = maximum value
+ * $min_unit = minimum unit value
+ * $min_space = minimum spacing
+ * $grid_division = fixed division size
+ * $units_before = text before units
+ * $units_after = text after units
+ * $decimal_digits = number of digits
+ * $text_callback = text formatting function
+ * $values = values array/object
+ * $log = logarithmic axis (bool)
+ * $log_base = log axis base
+ * $levels = axis levels
+ * $ticks = fixed axis ticks (array)
+ */
+ public function get($length, $min, $max, $min_unit, $min_space, $grid_division,
+ $units_before, $units_after, $decimal_digits, $text_callback, $values,
+ $log, $log_base, $levels, $ticks)
+ {
+ if($this->datetime) {
+ // datetime axis
+
+ if(is_array($ticks)) {
+ $axis = new AxisFixedTicksDateTime($length, $max, $min, $ticks,
+ $this->settings);
+ } else {
+ $axis = new AxisDateTime($length, $max, $min, $min_space,
+ $grid_division, $levels, $this->settings);
+ }
+
+ } elseif($log) {
+
+ // logarithmic axis
+ if(is_array($ticks)) {
+ $axis = new AxisLogTicks($length, $max, $min, $min_unit, $min_space,
+ $this->fit, $units_before, $units_after, $decimal_digits, $log_base,
+ $grid_division, $text_callback, $values, $ticks);
+ } else {
+ $axis = new AxisLog($length, $max, $min, $min_unit, $min_space,
+ $this->fit, $units_before, $units_after, $decimal_digits, $log_base,
+ $grid_division, $text_callback, $values);
+ }
+
+ } elseif(is_array($ticks)) {
+
+ // axis with fixed ticks
+ $axis = new AxisFixedTicks($length, $max, $min, $ticks, $units_before,
+ $units_after, $decimal_digits, $text_callback, $values);
+
+ } elseif($this->tightX()) {
+
+ // create a fixed-tick axis
+ $ticks = $this->getTicks($length, $min, $max, $grid_division, $min_unit, $min_space);
+ $axis = new AxisFixedTicks($length, $max, $min, $ticks, $units_before,
+ $units_after, $decimal_digits, $text_callback, $values);
+
+ } elseif(is_numeric($grid_division)) {
+
+ // fixed grid divisions
+ $axis = new AxisFixed($length, $max, $min, $grid_division,
+ $units_before, $units_after, $decimal_digits, $text_callback,
+ $values);
+
+ } else {
+
+ // calculated axis
+ $axis = new Axis($length, $max, $min, $min_unit, $min_space, $this->fit,
+ $units_before, $units_after, $decimal_digits,
+ $text_callback, $values);
+
+ }
+ if($this->bar)
+ $axis->bar();
+ if($this->reverse)
+ $axis->reverse();
+ return $axis;
+ }
+
+ /**
+ * Returns TRUE for an X-axis with no space at end
+ */
+ private function tightX()
+ {
+ return $this->fit && isset($this->settings['axis_tightness_x']) &&
+ $this->settings['axis_tightness_x'] > 0;
+ }
+
+ /**
+ * Returns a list of ticks for axis with no space at ends
+ */
+ private function getTicks($length, $min, $max, $division, $min_unit, $min_space)
+ {
+ $start = $min;
+ $end = $max;
+ if(is_numeric($division)) {
+ $step = $division;
+ } else {
+
+ // use an axis to calculate the divisions
+ $a = new Axis($length, $max, $min, $min_unit, $min_space, true, null, null,
+ 1, null, null);
+ if($this->bar)
+ $a->bar();
+ if($this->reverse)
+ $a->reverse();
+ $p = $a->getGridPoints(0);
+
+ $step = abs($p[0]->value - $p[1]->value);
+ $max_step = max(abs($start), abs($end));
+
+ // need smaller divisions
+ if(count($p) == 2 || $step > $max_step) {
+ $multipliers = [0.1, 0.2, 0.25, 0.5];
+ $mmax = count($multipliers) - 1;
+ $m = 0;
+ $mn = $min_space / $length;
+
+ for($i = $mmax; $i >= 0; --$i) {
+ $sm = $step * $multipliers[$i];
+ if($sm > $max_step)
+ continue;
+
+ if($multipliers[$i] < $mn)
+ break;
+ }
+
+ $step = $sm;
+ }
+ }
+
+ $start -= fmod($start, $step);
+ $ticks = range($start, $end, $step);
+ return $ticks;
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/AxisFixed.php b/classes/vendor/81x/goat1000/svggraph/AxisFixed.php
new file mode 100644
index 0000000..023bce8
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/AxisFixed.php
@@ -0,0 +1,89 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * Axis with fixed measurements
+ */
+class AxisFixed extends Axis {
+
+ protected $step;
+ protected $orig_max_value;
+ protected $orig_min_value;
+
+ public function __construct($length, $max_val, $min_val, $step,
+ $units_before, $units_after, $decimal_digits, $label_callback, $values)
+ {
+ // min_unit = 1, min_space = 1, fit = false
+ parent::__construct($length, $max_val, $min_val, 1, 1, false, $units_before,
+ $units_after, $decimal_digits, $label_callback, $values);
+ $this->orig_max_value = $max_val;
+ $this->orig_min_value = $min_val;
+ $this->step = $step;
+ }
+
+ /**
+ * Calculates a grid based on min, max and step
+ * min and max will be adjusted to fit step
+ */
+ protected function grid()
+ {
+ // use the original min/max to prevent compounding of floating-point
+ // rounding problems
+ $min = $this->orig_min_value;
+ $max = $this->orig_max_value;
+
+ // if min and max are the same side of 0, only adjust one of them
+ if($max * $min >= 0) {
+ $count = $max - $min;
+ if(abs($max) >= abs($min)) {
+ $this->max_value = $min + $this->step * ceil($count / $this->step);
+ } else {
+ $this->min_value = $max - $this->step * ceil($count / $this->step);
+ }
+ } else {
+ $this->max_value = $this->step * ceil($max / $this->step);
+ $this->min_value = $this->step * floor($min / $this->step);
+ }
+
+ $count = ($this->max_value - $this->min_value) / $this->step;
+ $ulen = $this->max_value - $this->min_value;
+ if($ulen == 0)
+ throw new \Exception('Zero length axis (min >= max)');
+ $this->unit_size = $this->length / $ulen;
+ $grid = $this->length / $count;
+ $this->zero = (-$this->min_value / $this->step) * $grid;
+ return $grid;
+ }
+
+ /**
+ * Sets the bar style, adding an extra unit
+ */
+ public function bar()
+ {
+ if(!$this->rounded_up) {
+ $this->orig_max_value += $this->min_unit;
+ parent::bar();
+ }
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/AxisFixedDoubleEnded.php b/classes/vendor/81x/goat1000/svggraph/AxisFixedDoubleEnded.php
new file mode 100644
index 0000000..80697bb
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/AxisFixedDoubleEnded.php
@@ -0,0 +1,71 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * Axis with fixed measurements
+ */
+class AxisFixedDoubleEnded extends AxisDoubleEnded {
+
+ protected $step;
+
+ public function __construct($length, $max_val, $min_val, $step,
+ $units_before, $units_after, $decimal_digits, $label_callback)
+ {
+ // min_unit = 1, min_space = 1, fit = false
+ parent::__construct($length, $max_val, $min_val, 1, 1, false, $units_before,
+ $units_after, $decimal_digits, $label_callback);
+ $this->step = $step;
+ }
+
+ /**
+ * Calculates a grid based on min, max and step
+ * min and max will be adjusted to fit step
+ */
+ protected function grid()
+ {
+ // if min and max are the same side of 0, only adjust one of them
+ if($this->max_value * $this->min_value >= 0) {
+ $count = $this->max_value - $this->min_value;
+ if(abs($this->max_value) >= abs($this->min_value)) {
+ $this->max_value = $this->min_value +
+ $this->step * ceil($count / $this->step);
+ } else {
+ $this->min_value = $this->max_value -
+ $this->step * ceil($count / $this->step);
+ }
+ } else {
+ $this->max_value = $this->step * ceil($this->max_value / $this->step);
+ $this->min_value = $this->step * floor($this->min_value / $this->step);
+ }
+
+ $count = ($this->max_value - $this->min_value) / $this->step;
+ $ulen = $this->max_value - $this->min_value;
+ if($ulen == 0)
+ throw new \Exception('Zero length axis');
+ $this->unit_size = $this->length / $ulen;
+ $grid = $this->length / $count;
+ $this->zero = (-$this->min_value / $this->step) * $grid;
+ return $grid;
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/AxisFixedTicks.php b/classes/vendor/81x/goat1000/svggraph/AxisFixedTicks.php
new file mode 100644
index 0000000..e51097c
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/AxisFixedTicks.php
@@ -0,0 +1,122 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * Class for axis with specific tick marks
+ */
+class AxisFixedTicks extends Axis {
+
+ protected $ticks;
+ protected $unit_length = 0;
+
+ public function __construct($length, $max, $min, $ticks, $units_before,
+ $units_after, $decimal_digits, $label_callback, $values)
+ {
+ sort($ticks);
+ $this->ticks = [];
+
+ // only keep the ticks that are inside the axis bounds
+ foreach($ticks as $t) {
+ if($t >= $min && $t <= $max)
+ $this->ticks[] = $t;
+ }
+
+ if(count($this->ticks) < 1)
+ throw new \Exception('No ticks in axis range');
+
+ $this->unit_length = $max - $min;
+ if($this->unit_length == 0)
+ throw new \Exception('Zero length axis (min >= max)');
+
+ // min_unit = 1, min_space = 1, fit = false
+ parent::__construct($length, $max, $min, 1, 1, false, $units_before,
+ $units_after, $decimal_digits, $label_callback, $values);
+
+ $this->setLength($length);
+ }
+
+ /**
+ * For bar graphs, increase length by 1 unit
+ */
+ public function bar()
+ {
+ $this->unit_length++;
+ $this->setLength($this->length);
+ parent::bar();
+ }
+
+ /**
+ * Returns the size of a unit in grid space
+ */
+ public function unit()
+ {
+ return $this->unit_size;
+ }
+
+ /**
+ * Returns the distance along the axis where 0 should be
+ */
+ public function zero()
+ {
+ return $this->zero;
+ }
+
+ /**
+ * Set length, adjust scaling
+ */
+ public function setLength($l)
+ {
+ $this->length = $l;
+
+ // these values are fixed, based on length
+ $this->unit_size = $this->length / $this->unit_length;
+ $this->zero = -$this->min_value * $this->unit_size;
+
+ // not used by this class, but others expect it to be set
+ $this->grid_spacing = 1;
+ }
+
+ /**
+ * Returns the grid points as an array of GridPoints
+ * if $start is NULL, just set up the grid spacing without returning points
+ */
+ public function getGridPoints($start)
+ {
+ if($start === null)
+ return;
+
+ $points = [];
+ foreach($this->ticks as $value) {
+ $position = $start + $this->direction * ($this->zero + $value * $this->unit_size);
+ $points[] = $this->getGridPoint($position, $value);
+ }
+
+ if($this->direction < 0) {
+ usort($points, function($a, $b) { return $b->position - $a->position; });
+ } else {
+ usort($points, function($a, $b) { return $a->position - $b->position; });
+ }
+
+ return $points;
+ }
+}
diff --git a/classes/vendor/81x/goat1000/svggraph/AxisFixedTicksDateTime.php b/classes/vendor/81x/goat1000/svggraph/AxisFixedTicksDateTime.php
new file mode 100644
index 0000000..16231c5
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/AxisFixedTicksDateTime.php
@@ -0,0 +1,75 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * Class for axis with specific tick marks and date/time keys
+ */
+class AxisFixedTicksDateTime extends AxisDateTime {
+
+ protected $ticks;
+
+ public function __construct($length, $max, $min, $ticks, $options)
+ {
+ // only keep the ticks that are inside the axis bounds
+ $this->ticks = [];
+ foreach($ticks as $tstr) {
+ $t = Graph::dateConvert($tstr);
+ if($t === null)
+ throw new \Exception('Ticks not in correct date/time format');
+
+ if($t >= $min && $t <= $max)
+ $this->ticks[] = $t;
+ }
+
+ if(count($this->ticks) < 1)
+ throw new \Exception('No ticks in axis range');
+
+ // min_space = 1, fixed_division = null, levels = null,
+ parent::__construct($length, $max, $min, 1, null, null, $options);
+ }
+
+ /**
+ * Returns the grid points as an array of GridPoints
+ * if $start is NULL, just set up the grid spacing without returning points
+ */
+ public function getGridPoints($start)
+ {
+ if($start === null)
+ return;
+
+ $points = [];
+ foreach($this->ticks as $value) {
+ $pos = $this->position($value);
+ $position = $start + ($pos * $this->direction);
+ $points[] = $this->getGridPoint($position, $value);
+ }
+
+ if($this->direction < 0) {
+ usort($points, function($a, $b) { return $b->position - $a->position; });
+ } else {
+ usort($points, function($a, $b) { return $a->position - $b->position; });
+ }
+
+ return $points;
+ }
+}
diff --git a/classes/vendor/81x/goat1000/svggraph/AxisLog.php b/classes/vendor/81x/goat1000/svggraph/AxisLog.php
new file mode 100644
index 0000000..5feabcc
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/AxisLog.php
@@ -0,0 +1,295 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * Class for calculating logarithmic axis measurements
+ */
+class AxisLog extends Axis {
+
+ protected $lgmin;
+ protected $lgmax;
+ protected $base = 10;
+ protected $divisions;
+ protected $grid_space;
+ protected $grid_split = 0;
+ protected $negative = false;
+ protected $lgmul;
+ protected $int_base = true;
+
+ public function __construct($length, $max_val, $min_val, $min_unit,
+ $min_space, $fit, $units_before, $units_after, $decimal_digits,
+ $base, $divisions, $label_callback, $values)
+ {
+ if($min_val == 0 || $max_val == 0)
+ throw new \Exception('0 value on log axis');
+ if($min_val < 0 && $max_val > 0)
+ throw new \Exception('-ve and +ve on log axis');
+ if($max_val <= $min_val && $min_unit == 0)
+ throw new \Exception('Zero length axis (min >= max)');
+ $this->length = $length;
+ $this->min_unit = $min_unit;
+ $this->min_space = $min_space;
+ $this->units_before = $units_before;
+ $this->units_after = $units_after;
+ $this->decimal_digits = $decimal_digits;
+ $this->label_callback = $label_callback;
+ $this->values = $values;
+ if(is_numeric($base) && $base > 1) {
+ $this->base = $base * 1.0;
+ $this->int_base = $this->base == floor($this->base);
+ }
+ $this->uneven = false;
+ if($min_val < 0) {
+ $this->negative = true;
+ $m = $min_val;
+ $min_val = abs($max_val);
+ $max_val = abs($m);
+ }
+ if(is_numeric($divisions))
+ $this->divisions = $divisions;
+
+ if($fit && $this->int_base) {
+ // X-axis, try to use values
+ $lgminf = $this->bfloor($min_val);
+ $lgmaxf = $this->bceil($max_val);
+ $this->lgmin = log($lgminf, $this->base);
+ $this->lgmax = log($lgmaxf, $this->base);
+ } else {
+ // Y-axis, allow space
+ $this->lgmin = floor(log($min_val, $this->base));
+ $this->lgmax = ceil(log($max_val, $this->base));
+ }
+
+ // if all the values are the same, and a power of the base
+ if($this->lgmax <= $this->lgmin)
+ $this->lgmin -= 1.0;
+
+ $this->lgmul = $this->length / ($this->lgmax - $this->lgmin);
+ $this->min_value = pow($this->base, $this->lgmin);
+ $this->max_value = pow($this->base, $this->lgmax);
+ }
+
+ /**
+ * Floor to the nearest sensible number
+ */
+ protected function bfloor($value)
+ {
+ $b = floor(log($value, $this->base));
+ $bf = pow($this->base, $b);
+ $m = floor($value / $bf);
+ return $m * $bf;
+ }
+
+ /**
+ * Ceil to the nearest sensible number
+ */
+ protected function bceil($value)
+ {
+ $b = floor(log($value, $this->base));
+ $bf = pow($this->base, $b);
+ $m = ceil($value / $bf);
+ return $m * $bf;
+ }
+
+ /**
+ * Returns the grid points as an associative array:
+ * array($value => $position)
+ */
+ public function getGridPoints($start)
+ {
+ if($start === null)
+ return;
+
+ $pow_div = ceil($this->lgmax) - floor($this->lgmin);
+ $this->grid_space = $this->length / $pow_div;
+
+ $this->grid_split = $this->divisions ? $this->divisions :
+ $this->findDivision($this->grid_space, $this->min_space);
+
+ $spoints = [];
+ if($this->grid_split) {
+ for($l = $this->grid_split; $l < $this->base; $l += $this->grid_split)
+ $spoints[] = log($l, $this->base);
+ }
+
+ $points = [];
+ $val = $this->min_value;
+ while($val <= $this->max_value) {
+ $value = $this->negative ? -$val : $val;
+ $position = $this->position($value);
+ $position = $start + ($this->direction * $position);
+ $points[] = $this->getGridPoint($position, $value);
+
+ // inserted at start of loop, but calculated at end
+ if($val > ($this->max_value * 0.995))
+ break;
+
+ // find next point
+ $l = log($val, $this->base);
+ $l_floor = floor($l);
+ $l_dec = $l - $l_floor;
+ $l_next = 0;
+ foreach($spoints as $l1) {
+ if($l1 && ($l1 - $l_dec) > 0.001) {
+ $l_next = $l_floor + $l1;
+ break;
+ }
+ }
+ if(!$l_next || $l_next > $this->lgmax) {
+ // next full power or end of axis
+ $l_next = min($l_floor + 1, $this->lgmax);
+ }
+ $val = pow($this->base, $l_next);
+ }
+
+ if($this->direction < 0) {
+ usort($points, function($a, $b) { return $b->position - $a->position; });
+ } else {
+ usort($points, function($a, $b) { return $a->position - $b->position; });
+ }
+ return $points;
+ }
+
+ /**
+ * Returns the grid subdivision points as an array
+ */
+ public function getGridSubdivisions($min_space, $min_unit, $start, $fixed)
+ {
+ $points = [];
+ if($this->int_base) {
+ $split = $this->findDivision($this->grid_space, $min_space,
+ $this->grid_split);
+ if($split) {
+ for($l = $this->lgmin; $l < $this->lgmax; ++$l) {
+ for($l1 = $split; $l1 < $this->base; $l1 += $split) {
+ if($this->grid_split == 0 || $l1 % $this->grid_split) {
+ $p = log($l1, $this->base);
+ $val = pow($this->base, $l + $p);
+ $position = $start + $this->position($val) * $this->direction;
+ $points[] = new GridPoint($position, '', $val);
+ }
+ }
+ }
+ }
+ }
+ return $points;
+ }
+
+ /**
+ * Returns the distance in pixels $u takes from $pos
+ */
+ public function measureUnits($pos, $u)
+ {
+ $i = Coords::parseValue($pos);
+ if(!is_numeric($i['value']))
+ throw new \Exception("Unable to measure $u units from '{$i['value']}'");
+
+ $start_pos = $this->position($i['value']);
+ $end_pos = $this->position($i['value'] + $u);
+ return $end_pos - $start_pos;
+ }
+
+ /**
+ * Returns the position of a value on the axis, or NULL if the position is
+ * not possible
+ */
+ public function position($index, $item = null)
+ {
+ $value = $index;
+ if($item !== null && !$this->values->associativeKeys())
+ $value = $item->key;
+ if($this->negative) {
+ if($value >= 0)
+ return null;
+ $abs_value = abs($value);
+ if($abs_value < $this->min_value)
+ return null;
+ return $this->length - (log($abs_value, $this->base) - $this->lgmin) *
+ $this->lgmul;
+ }
+ if($value <= 0 || $value < $this->min_value)
+ return null;
+ return (log($value, $this->base) - $this->lgmin) * $this->lgmul;
+ }
+
+ /**
+ * Returns the position of the origin
+ */
+ public function origin()
+ {
+ // not the position of 0, because that doesn't exist
+ return $this->negative ? $this->length : 0;
+ }
+
+ /**
+ * Returns the value at a position on the axis
+ */
+ public function value($position)
+ {
+ $p = pow($this->base, $this->lgmin + $position / $this->lgmul);
+ return $p;
+ }
+
+ /**
+ * Finds an even division of the given space that is >= min_space
+ */
+ private function findDivision($space, $min_space, $main_division = 0)
+ {
+ $split = 0;
+ if($this->int_base) {
+ $division = $main_division ? $main_division : $this->base;
+ $l = $this->base - 1;
+ $lgs = $space * log($l, $this->base);
+
+ $smallest = $space - $lgs;
+ if($smallest < $min_space) {
+ $max_split = floor($division / 2);
+ for($i = 2; $smallest < $min_space && $i <= $max_split; ++$i) {
+ if($division % $i == 0) {
+ // the smallest gap is the one before the next power
+ $l = $this->base - $i;
+ $lgs = $space * log($l, $this->base);
+ $smallest = $space - $lgs;
+ $split = $i;
+ }
+ }
+ if($smallest < $min_space)
+ $split = 0;
+ } else {
+ $split = 1;
+ }
+ }
+ return $split;
+ }
+
+ /**
+ * Not actually 0, but the position of the axis
+ */
+ public function zero()
+ {
+ if($this->negative)
+ return $this->length;
+ return 0;
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/AxisLogTicks.php b/classes/vendor/81x/goat1000/svggraph/AxisLogTicks.php
new file mode 100644
index 0000000..f6ec527
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/AxisLogTicks.php
@@ -0,0 +1,75 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * Class for logarithmic axis with specific tick marks
+ */
+class AxisLogTicks extends AxisLog {
+
+ protected $ticks;
+
+ public function __construct($length, $max_val, $min_val, $min_unit,
+ $min_space, $fit, $units_before, $units_after, $decimal_digits,
+ $base, $divisions, $label_callback, $values, $ticks)
+ {
+ sort($ticks);
+ $this->ticks = [];
+
+ // only keep the ticks that are inside the axis bounds
+ foreach($ticks as $t) {
+ if($t >= $min_val && $t <= $max_val)
+ $this->ticks[] = $t;
+ }
+
+ if(count($this->ticks) < 1)
+ throw new \Exception('No ticks in axis range');
+
+ parent::__construct($length, $max_val, $min_val, $min_unit,
+ $min_space, $fit, $units_before, $units_after, $decimal_digits,
+ $base, $divisions, $label_callback, $values);
+ }
+
+ /**
+ * Returns the grid points as an array of GridPoints
+ */
+ public function getGridPoints($start)
+ {
+ if($start === null)
+ return;
+
+ $points = [];
+ foreach($this->ticks as $val) {
+ $position = $this->position($val);
+ $position = $start + ($this->direction * $position);
+ $points[] = $this->getGridPoint($position, $val);
+ }
+
+ if($this->direction < 0) {
+ usort($points, function($a, $b) { return $b->position - $a->position; });
+ } else {
+ usort($points, function($a, $b) { return $a->position - $b->position; });
+ }
+
+ return $points;
+ }
+}
diff --git a/classes/vendor/81x/goat1000/svggraph/Bar3D.php b/classes/vendor/81x/goat1000/svggraph/Bar3D.php
new file mode 100644
index 0000000..a46e715
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/Bar3D.php
@@ -0,0 +1,250 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class Bar3D {
+
+ protected $graph;
+ protected $overlay_top;
+ protected $overlay_side;
+ protected $overlay_front;
+ protected $overlay_top_colour;
+ protected $overlay_side_colour;
+ protected $overlay_front_colour;
+ protected $skew_top;
+ protected $skew_side;
+ protected $angle;
+ protected $depth;
+ protected $z_cos_a = 10;
+ protected $z_sin_a = 10;
+ protected $solid_colour = 'none';
+
+ public function __construct(&$graph)
+ {
+ $this->graph = $graph;
+ $this->overlay_top = min(1, max(0, $graph->getOption('bar_top_overlay_opacity')));
+ $this->overlay_side = min(1, max(0, $graph->getOption('bar_side_overlay_opacity')));
+ $this->overlay_front = min(1, max(0, $graph->getOption('bar_front_overlay_opacity')));
+ if($this->overlay_top)
+ $this->overlay_top_colour = new Colour($graph, $graph->getOption('bar_top_overlay_colour'));
+ if($this->overlay_side)
+ $this->overlay_side_colour = new Colour($graph, $graph->getOption('bar_side_overlay_colour'));
+ if($this->overlay_front)
+ $this->overlay_front_colour = new Colour($graph, $graph->getOption('bar_front_overlay_colour'));
+ $this->skew_top = (bool)$graph->getOption('skew_top');
+ $this->skew_side = (bool)$graph->getOption('skew_side');
+ $this->angle = min(89, max(1, $graph->getOption('project_angle', 30)));
+ }
+
+ /**
+ * Sets the depth of the bar
+ */
+ public function setDepth($d)
+ {
+ $this->depth = $d;
+ $a = deg2rad($this->angle);
+ $this->z_cos_a = $d * cos($a);
+ $this->z_sin_a = $d * sin($a);
+ }
+
+ /**
+ * Projects x,y at fixed depth
+ */
+ protected function project($x, $y)
+ {
+ return [$x + $this->z_cos_a, $y - $this->z_sin_a];
+ }
+
+ /**
+ * Draws the bar
+ */
+ public function draw($x, $y, $w, $h, $draw_top, $draw_side, $solid_colour)
+ {
+ $this->solid_colour = $solid_colour;
+ $bar = $this->front($x, $y, $w, $h);
+ if($draw_top)
+ $bar .= $this->top($x, $y, $w, $h);
+ if($draw_side)
+ $bar .= $this->side($x, $y, $w, $h);
+ $bar .= $this->edge($x, $y, $w, $h);
+ return $bar;
+ }
+
+ /**
+ * Draws bar front
+ */
+ protected function front($x, $y, $w, $h)
+ {
+ $f = ['x' => $x, 'y' => $y, 'width' => $w, 'height' => $h, 'stroke' => 'none'];
+ $front = $this->graph->element('rect', $f);
+ if($this->overlay_front) {
+ $f['fill-opacity'] = $this->overlay_front;
+ $f['fill'] = $this->overlay_front_colour;
+ $front .= $this->graph->element('rect', $f);
+ }
+ return $front;
+ }
+
+ /**
+ * Draws bar top
+ */
+ protected function top($x, $y, $w, $h)
+ {
+ $t = $this->top_path($w);
+ $xform = new Transform;
+ $xform->translate($x, $y);
+
+ // skewing?
+ if(isset($t['transform']))
+ $xform->add($t['transform']);
+ else
+ $t['fill'] = $this->solid_colour;
+ $t['transform'] = $xform;
+ $top = $this->graph->element('path', $t);
+
+ if($this->overlay_top) {
+ $t['fill-opacity'] = $this->overlay_top;
+ $t['fill'] = $this->overlay_top_colour;
+ $top .= $this->graph->element('path', $t);
+ }
+ return $top;
+ }
+
+ /**
+ * Draws bar side
+ */
+ protected function side($x, $y, $w, $h)
+ {
+ $s = $this->side_path($h);
+ $xform = new Transform;
+ $xform->translate($x + $w, $y);
+ if(isset($s['transform']))
+ $xform->add($s['transform']);
+ $s['transform'] = $xform;
+ $side = $this->graph->element('path', $s);
+
+ if($this->overlay_side) {
+ $s['fill-opacity'] = $this->overlay_side;
+ $s['fill'] = $this->overlay_side_colour;
+ $side .= $this->graph->element('path', $s);
+ }
+ return $side;
+ }
+
+ /**
+ * Draws bar edge path
+ */
+ protected function edge($x, $y, $w, $h)
+ {
+ $e = [];
+ if($h > 0) {
+ $e['d'] = new PathData(
+ // surround
+ 'M', $x, $y + $h,
+ 'l', 0, -$h,
+ 'l', $this->z_cos_a, -$this->z_sin_a,
+ 'l', $w, 0,
+ 'l', 0, $h,
+ 'l', -$this->z_cos_a, $this->z_sin_a, 'z',
+
+ // vertical
+ 'M', $x + $w, $y,
+ 'v', $h,
+
+ // top front and side
+ 'M', $x, $y,
+ 'l', $w, 0,
+ 'l', $this->z_cos_a, -$this->z_sin_a);
+ } else {
+ $e['d'] = new PathData('M', $x, $y,
+ 'l', $this->z_cos_a, -$this->z_sin_a,
+ 'l', $w, 0,
+ 'l', -$this->z_cos_a, $this->z_sin_a, 'z');
+ }
+ $e['fill'] = 'none';
+ return $this->graph->element('path', $e);
+ }
+
+ /**
+ * Returns path array for a bar top
+ */
+ public function top_path($bw)
+ {
+ $top = [];
+ if($this->skew_top) {
+ $top['d'] = new PathData('M', 0, 0,
+ 'l', 0, -$this->depth,
+ 'l', $bw, 0,
+ 'l', 0, $this->depth, 'z');
+ $top['transform'] = $this->skew(true);
+ } else {
+ $top['d'] = new PathData('M', 0, 0,
+ 'l', $bw, 0,
+ 'l', $this->z_cos_a, -$this->z_sin_a,
+ 'l', -$bw, 0, 'z');
+ }
+ $top['stroke'] = 'none';
+ return $top;
+ }
+
+ /**
+ * Returns path array for a bar side
+ */
+ public function side_path($bh)
+ {
+ $side = [];
+ if($this->skew_side) {
+ $side['d'] = new PathData('M', 0, 0,
+ 'L', $this->depth, 0,
+ 'l', 0, $bh,
+ 'l', -$this->depth, 0, 'z');
+ $side['transform'] = $this->skew(false);
+ } else {
+ $side['d'] = new PathData('M', 0, 0,
+ 'l', $this->z_cos_a, -$this->z_sin_a,
+ 'l', 0, $bh,
+ 'l', -$this->z_cos_a, $this->z_sin_a, 'z');
+ }
+ $side['stroke'] = 'none';
+ return $side;
+ }
+
+ /**
+ * Returns the transform for skewing side or top
+ */
+ protected function skew($top)
+ {
+ $xform = new Transform;
+ $s_x = 1;
+ $s_y = 1;
+ if($top) {
+ $xform->skewX(-90 + $this->angle);
+ $s_y = $this->z_sin_a / $this->depth;
+ } else {
+ $xform->skewY(-$this->angle);
+ $s_x = $this->z_cos_a / $this->depth;
+ }
+
+ $xform->scale($s_x, $s_y);
+ return $xform;
+ }
+}
diff --git a/classes/vendor/81x/goat1000/svggraph/Bar3DCylinder.php b/classes/vendor/81x/goat1000/svggraph/Bar3DCylinder.php
new file mode 100644
index 0000000..19077cd
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/Bar3DCylinder.php
@@ -0,0 +1,154 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class Bar3DCylinder extends Bar3D {
+
+ protected $ellipse;
+ protected $arc_path;
+ protected $cyl_offset_x;
+ protected $cyl_offset_y;
+ protected $shade_gradient_id;
+
+ public function __construct(&$graph)
+ {
+ parent::__construct($graph);
+ $gradient = $graph->getOption('depth_shade_gradient');
+ if(is_array($gradient))
+ $this->shade_gradient_id = $graph->defs->addGradient($gradient);
+ }
+
+ /**
+ * Calculates the a and b radii of the ellipse filling the parallelogram
+ */
+ protected function findEllipse()
+ {
+ $alpha = deg2rad($this->angle / 2);
+ $x = $this->depth * cos($alpha) / 2;
+ $y = $this->depth * sin($alpha) / 2;
+ $dydx = -$y / $x;
+
+ $bsq = pow($y, 2) - $x * $y * $dydx;
+ $asq = pow($x, 2) / (1 - $y / ($y - $x * $dydx));
+
+ $a = sqrt($asq);
+ $b = sqrt($bsq);
+
+ // now find the vertical
+ $alpha2 = deg2rad(- $this->angle / 2 - 90);
+ $dydx2 = tan($alpha2);
+ $ysq = $bsq / (pow($dydx2, 2) * ($asq / $bsq) + 1);
+ $xsq = $asq - $asq * $ysq / $bsq;
+
+ $x1 = sqrt($xsq);
+ $y1 = -sqrt($ysq);
+ $this->ellipse = compact('a', 'b', 'x1', 'y1');
+
+ // create the arc path
+ $r = -$this->angle / 2;
+ $rr = deg2rad($r);
+ $x1a = -($x1 * cos($rr) + $y1 * sin($rr));
+ $y1a = -($x1 * sin($rr) - $y1 * cos($rr));
+ $x2 = -2 * $x1a;
+ $y2 = -2 * $y1a;
+ $this->cyl_offset_x = $x1a;
+ $this->cyl_offset_y = $y1a;
+ $this->arc_path = new PathData('a', $a, $b, $r, 1, 0, $x2, $y2);
+ }
+
+ /**
+ * Sets the depth of the bar
+ */
+ public function setDepth($d)
+ {
+ parent::setDepth($d);
+ $this->findEllipse();
+ }
+
+ /**
+ * Draws bar front
+ */
+ protected function front($x, $y, $w, $h)
+ {
+ $x += $this->cyl_offset_x;
+ $y += $this->cyl_offset_y;
+ $path = new PathData('M', $x, $y, 'v', $h);
+ $path->add($this->arc_path);
+ $path->add('v', -$h);
+ $f = ['d' => $path, 'stroke' => 'none'];
+ $front = $this->graph->element('path', $f);
+
+ if($this->shade_gradient_id) {
+ $f['fill'] = 'url(#' . $this->shade_gradient_id . ')';
+ $front .= $this->graph->element('path', $f);
+ }
+
+ $f = ['d' => $path, 'fill' => 'none', 'stroke-linejoin' => 'bevel'];
+ $front .= $this->graph->element('path', $f);
+ return $front;
+ }
+
+ /**
+ * Draws bar top
+ */
+ protected function top($x, $y, $w, $h)
+ {
+ $r = -$this->angle / 2;
+ $xform = new Transform;
+ $xform->translate($x, $y);
+ $xform->rotate($r);
+ $t = [
+ 'cx' => 0, 'cy' => 0,
+ 'rx' => $this->ellipse['a'], 'ry' => $this->ellipse['b'],
+ 'transform' => $xform,
+ 'fill' => $this->solid_colour,
+ ];
+ if($this->overlay_top)
+ $t['stroke'] = 'none';
+ $top = $this->graph->element('ellipse', $t);
+
+ if($this->overlay_top) {
+ $t['fill-opacity'] = $this->overlay_top;
+ $t['fill'] = $this->overlay_top_colour;
+ unset($t['stroke']);
+ $top .= $this->graph->element('ellipse', $t);
+ }
+ return $top;
+ }
+
+ /**
+ * Draws bar side
+ */
+ protected function side($x, $y, $w, $h)
+ {
+ return '';
+ }
+
+ /**
+ * Draws bar edge path
+ */
+ protected function edge($x, $y, $w, $h)
+ {
+ // cylinder edge is drawn with front
+ return '';
+ }
+}
diff --git a/classes/vendor/81x/goat1000/svggraph/Bar3DGraph.php b/classes/vendor/81x/goat1000/svggraph/Bar3DGraph.php
new file mode 100644
index 0000000..6757d1e
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/Bar3DGraph.php
@@ -0,0 +1,92 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class Bar3DGraph extends ThreeDGraph {
+
+ use BarGraphTrait {
+ barGroup as traitBarGroup;
+ setBarWidth as traitSetBarWidth;
+ }
+
+ protected $tx;
+ protected $ty;
+ protected $bar_class = 'Goat1000\\SVGGraph\\Bar3D';
+ protected $bar_drawer;
+
+ public function __construct($w, $h, array $settings, array $fixed_settings = [])
+ {
+ $fs = ['label_centre' => !isset($settings['datetime_keys'])];
+ $fs = array_merge($fs, $fixed_settings);
+ parent::__construct($w, $h, $settings, $fs);
+
+ $this->bar_drawer = new $this->bar_class($this);
+ }
+
+ /**
+ * Returns the SVG code for a 3D bar
+ */
+ protected function bar3D($item, &$bar, $top, $index, $dataset = null,
+ $start = null, $axis = null)
+ {
+ $pos = $this->barY($item->value, $tmp_bar, $start, $axis);
+ if($pos === null || $pos > $this->height - $this->pad_bottom)
+ return '';
+
+ return $this->bar_drawer->draw($bar['x'], $bar['y'],
+ $bar['width'], $bar['height'], $top, true,
+ $this->getColour($item, $index, $dataset, false, false));
+ }
+
+ /**
+ * Set the bar width and space, create the top
+ */
+ protected function setBarWidth($width, $space)
+ {
+ $this->traitSetBarWidth($width, $space);
+ $this->bar_drawer->setDepth($width);
+ list($this->tx, $this->ty) = $this->project(0, 0, $space);
+ }
+
+ /**
+ * Add the translation to the bar group
+ */
+ protected function barGroup()
+ {
+ $group = $this->traitBarGroup();
+ if($this->tx || $this->ty) {
+ $xform = new Transform;
+ $xform->translate($this->tx, $this->ty);
+ $group['transform'] = $xform;
+ }
+ return $group;
+ }
+
+ /**
+ * Returns the SVG code for a bar
+ */
+ protected function drawBar(DataItem $item, $index, $start = 0, $axis = null,
+ $dataset = 0, $options = [])
+ {
+ return $this->drawBar3D($item, $index, $start, $axis, $dataset, $options);
+ }
+}
diff --git a/classes/vendor/81x/goat1000/svggraph/BarAndLineGraph.php b/classes/vendor/81x/goat1000/svggraph/BarAndLineGraph.php
new file mode 100644
index 0000000..c359ef4
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/BarAndLineGraph.php
@@ -0,0 +1,260 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class BarAndLineGraph extends GroupedBarGraph {
+
+ protected $linegraph = null;
+ protected $line_datasets = [];
+ protected $bar_datasets = [];
+
+ /**
+ * We need an instance of the LineGraph class
+ */
+ public function __construct($w, $h, array $settings, array $fixed_settings = [])
+ {
+ parent::__construct($w, $h, $settings, $fixed_settings);
+
+ // prevent repeated labels
+ unset($settings['label']);
+ $this->linegraph = new MultiLineGraph($w, $h, $settings);
+ }
+
+ /**
+ * Draws the bars and lines
+ */
+ protected function draw()
+ {
+ $body = $this->grid() . $this->underShapes();
+
+ // LineGraph has not been initialised, need to copy in details
+ $copy = ['colours', 'links', 'x_axes', 'y_axes', 'main_x_axis',
+ 'main_y_axis', 'legend',
+ // needed for best-fit line support
+ 'g_width', 'g_height', 'pad_left', 'pad_top'];
+ foreach($copy as $member)
+ $this->linegraph->{$member} = $this->{$member};
+
+ // keep gradients and patterns synced
+ $this->linegraph->defs =& $this->defs;
+
+ // find the lines
+ $chunk_count = count($this->multi_graph);
+ $lines = $this->getOption('line_dataset');
+ $line_breaks = [];
+ $line_points = [];
+ $line_offsets = [];
+ $points = [];
+ if(!is_array($lines))
+ $lines = [$lines];
+ rsort($lines);
+ foreach($lines as $line) {
+ $line_breaks[$line] = $this->getOption(['line_breaks', $line]);
+ $line_points[$line] = [];
+ $points[$line] = [];
+ }
+ $this->line_datasets = $lines = array_flip($lines);
+
+ $y_axis_pos = $this->height - $this->pad_bottom -
+ $this->y_axes[$this->main_y_axis]->zero();
+ $y_bottom = min($y_axis_pos, $this->height - $this->pad_bottom);
+
+ // set up bars, then find line offsets
+ $this->barSetup();
+ foreach($lines as $k => $l)
+ $line_offsets[$k] = $this->getLineOffset($k);
+
+ // draw bars, store line points
+ $datasets = $this->multi_graph->getEnabledDatasets();
+ $line_dataset = 0;
+ $bars = '';
+ foreach($this->multi_graph as $bnum => $itemlist) {
+ $item = $itemlist[0];
+ $bar_pos = $this->gridPosition($item, $bnum);
+ if($bar_pos !== null) {
+ for($j = 0; $j < $chunk_count; ++$j) {
+ if(!in_array($j, $datasets))
+ continue;
+ $y_axis = $this->datasetYAxis($j);
+ $item = $itemlist[$j];
+
+ if(array_key_exists($j, $lines)) {
+ $line_dataset = $j;
+ if($line_breaks[$line_dataset] && $item->value === null &&
+ count($points[$line_dataset]) > 0) {
+ $line_points[$line_dataset][] = $points[$line_dataset];
+ $points[$line_dataset] = [];
+ } elseif($item->value !== null) {
+ $x = $bar_pos + $line_offsets[$line_dataset];
+ $y = $this->gridY($item->value, $y_axis);
+ $points[$line_dataset][] = [$x, $y, $item, $line_dataset, $bnum];
+ }
+ continue;
+ }
+
+ $this->setBarLegendEntry($j, $bnum, $item);
+ $bars .= $this->drawBar($item, $bnum, 0, $y_axis, $j);
+ }
+ }
+ }
+
+ foreach($points as $line_dataset => $line) {
+ if(!empty($line))
+ $line_points[$line_dataset][] = $line;
+ }
+
+ // draw lines clipped to grid
+ $graph_line = '';
+ foreach($line_points as $dataset => $points) {
+ foreach($points as $p) {
+ $graph_line .= $this->drawLine($dataset, $p, $y_bottom);
+ }
+ }
+ $group = [];
+ $this->clipGrid($group);
+ list($best_fit_above, $best_fit_below) = $this->linegraph->bestFitLines();
+ $bars .= $best_fit_below;
+ $bars .= $this->element('g', $group, null, $graph_line);
+
+ $group = [];
+ if($this->getOption('semantic_classes'))
+ $group['class'] = 'series';
+ $shadow_id = $this->defs->getShadow();
+ if($shadow_id !== null)
+ $group['filter'] = 'url(#' . $shadow_id . ')';
+ if(!empty($group))
+ $bars = $this->element('g', $group, null, $bars);
+ $body .= $bars;
+
+ $body .= $this->overShapes();
+ $body .= $this->axes();
+
+ // add in the markers created by line graph
+ $body .= $this->linegraph->drawMarkers();
+ $body .= $best_fit_above;
+
+ return $body;
+ }
+
+ /**
+ * Sets up bar details
+ */
+ protected function barSetup()
+ {
+ parent::barSetup();
+ $datasets = $this->multi_graph->getEnabledDatasets();
+ $chunk_count = count($datasets);
+ $bar = 0;
+ foreach($datasets as $i) {
+ // reduce chunk count for lines, add bars to list
+ if(array_key_exists($i, $this->line_datasets))
+ --$chunk_count;
+ else
+ $this->bar_datasets[$i] = $bar++;
+ }
+
+ if(count($this->bar_datasets) < 1)
+ throw new \Exception('No bar datasets enabled');
+
+ list($chunk_width, $bspace, $chunk_unit_width) =
+ $this->barPosition($this->getOption('bar_width'), $this->getOption('bar_width_min'),
+ $this->x_axes[$this->main_x_axis]->unit(), $chunk_count, $this->getOption('bar_space'),
+ $this->getOption('group_space'));
+ $this->group_bar_spacing = $chunk_unit_width;
+ $this->setBarWidth($chunk_width, $bspace);
+ }
+
+ /**
+ * Fills in the x and width of bar
+ */
+ protected function barX($item, $index, &$bar, $axis, $dataset)
+ {
+ $bar_x = $this->gridPosition($item, $index);
+ if($bar_x === null)
+ return null;
+
+ // relative position of bars stored in barSetup()
+ $bar['x'] = $bar_x + $this->calculated_bar_space +
+ ($this->bar_datasets[$dataset] * $this->group_bar_spacing);
+ $bar['width'] = $this->calculated_bar_width;
+ return $bar_x;
+ }
+
+ /**
+ * Return box or line for legend
+ */
+ public function drawLegendEntry($x, $y, $w, $h, $entry)
+ {
+ if(isset($entry->style['line_style']))
+ return $this->linegraph->drawLegendEntry($x, $y, $w, $h, $entry);
+ return parent::drawLegendEntry($x, $y, $w, $h, $entry);
+ }
+
+ /**
+ * Draws this graph's data labels, and the line graph's data labels
+ */
+ protected function drawDataLabels()
+ {
+ $labels = parent::drawDataLabels();
+ $labels .= $this->linegraph->drawDataLabels();
+ return $labels;
+ }
+
+ /**
+ * Returns the normal dataset order
+ */
+ public function getLegendOrder()
+ {
+ $datasets = count($this->multi_graph);
+ return range(0, $datasets - 1);
+ }
+
+ /**
+ * Returns the horizontal offset of the line relative to the grid
+ */
+ public function getLineOffset($dataset)
+ {
+ if($this->getOption('datetime_keys'))
+ return 0;
+
+ $offset = 0;
+ $o = $this->getOption(['line_bar', $dataset]);
+ if(is_numeric($o) && in_array($o, $this->bar_datasets)) {
+ $offset = $this->calculated_bar_space +
+ ($this->dataset_offsets[$o] * $this->group_bar_spacing) +
+ ($this->calculated_bar_width / 2);
+ } else {
+ $g_width = $this->x_axes[$this->main_x_axis]->unit();
+ $offset = $g_width / 2;
+ }
+ return $offset;
+ }
+
+ /**
+ * Draws a single line
+ */
+ public function drawLine($dataset, $points, $y_bottom)
+ {
+ return $this->linegraph->drawLine($dataset, $points, $y_bottom);
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/BarGraph.php b/classes/vendor/81x/goat1000/svggraph/BarGraph.php
new file mode 100644
index 0000000..ec56777
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/BarGraph.php
@@ -0,0 +1,39 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class BarGraph extends GridGraph {
+
+ use BarGraphTrait;
+
+ public function __construct($w, $h, array $settings, array $fixed_settings = [])
+ {
+ // backwards compatibility
+ if(isset($settings['show_bar_labels']) && !isset($settings['show_data_labels']))
+ $settings['show_data_labels'] = $settings['show_bar_labels'];
+
+ $fs = ['label_centre' => !isset($settings['datetime_keys'])];
+ $fs = array_merge($fs, $fixed_settings);
+ parent::__construct($w, $h, $settings, $fs);
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/BarGraphTrait.php b/classes/vendor/81x/goat1000/svggraph/BarGraphTrait.php
new file mode 100644
index 0000000..d2abfdd
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/BarGraphTrait.php
@@ -0,0 +1,301 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+trait BarGraphTrait {
+
+ // these are filled in by barSetup()
+ protected $calculated_bar_width;
+ protected $calculated_bar_space;
+
+ /**
+ * Draws the graph
+ */
+ protected function draw()
+ {
+ $this->setup();
+
+ $body = $this->grid();
+ $bars = $this->drawBars();
+ $bar_group = $this->barGroup();
+ if(!empty($bar_group))
+ $bars = $this->element('g', $bar_group, null, $bars);
+
+ $body .= $this->underShapes();
+ $body .= $bars;
+ $body .= $this->overShapes();
+ $body .= $this->axes();
+ return $body;
+ }
+
+ /**
+ * Draws the bars
+ */
+ protected function drawBars()
+ {
+ $dataset = $this->getOption(['dataset', 0], 0);
+ $this->barSetup();
+
+ $bars = '';
+ foreach($this->values[$dataset] as $bnum => $item) {
+ $this->setBarLegendEntry($dataset, $bnum, $item);
+ $bars .= $this->drawBar($item, $bnum, 0, null, $dataset);
+ }
+ return $bars;
+ }
+
+ /**
+ * Returns the width of a bar
+ */
+ protected function barWidth()
+ {
+ $bw = $this->getOption('bar_width');
+ if(is_numeric($bw) && $bw >= 1)
+ return $bw;
+ $unit_w = $this->x_axes[$this->main_x_axis]->unit();
+ $bw = $unit_w - $this->getOption('bar_space');
+ return max(1, $bw, $this->getOption('bar_width_min'));
+ }
+
+ /**
+ * Initialize bars
+ */
+ protected function barSetup()
+ {
+ $width = $this->barWidth();
+ $space = $this->barSpace($width);
+ $this->setBarWidth($width, $space);
+ }
+
+ /**
+ * Sets up the width of the bar and the spacing
+ */
+ protected function setBarWidth($width, $space)
+ {
+ $this->calculated_bar_width = $width;
+ $this->calculated_bar_space = $this->getOption('datetime_keys') ? 0 : $space;
+ }
+
+ /**
+ * Returns an array of attributes for whole group of bars
+ */
+ protected function barGroup()
+ {
+ $group = [];
+ if($this->getOption('semantic_classes'))
+ $group['class'] = 'series';
+ $shadow_id = $this->defs->getShadow();
+ if($shadow_id !== null)
+ $group['filter'] = 'url(#' . $shadow_id . ')';
+ return $group;
+ }
+
+ /**
+ * Sets the legend entry for a bar
+ */
+ protected function setBarLegendEntry($dataset, $index, DataItem $item)
+ {
+ $bar = ['fill' => $this->getColour($item, $index, $dataset)];
+ $this->setStroke($bar, $item, $index, $dataset);
+ $this->setLegendEntry($dataset, $index, $item, $bar);
+ }
+
+ /**
+ * Returns the SVG code for a bar
+ */
+ protected function drawBar(DataItem $item, $index, $start = 0, $axis = null,
+ $dataset = 0, $options = [])
+ {
+ if($item->value === null)
+ return '';
+
+ $bar = $this->barDimensions($item, $index, $start, $axis, $dataset);
+ if(empty($bar))
+ return '';
+
+ // if the bar is empty and no legend or labels to show give up now
+ if((string)$bar['height'] == '0' && !$this->getOption('legend_show_empty') &&
+ !$this->getOption('show_data_labels'))
+ return '';
+
+ $this->setStroke($bar, $item, $index, $dataset);
+ $bar['fill'] = $this->getColour($item, $index, $dataset);
+
+ if($this->getOption('semantic_classes'))
+ $bar['class'] = 'series' . $dataset;
+
+ $label_shown = $this->addDataLabel($dataset, $index, $bar, $item,
+ $bar['x'], $bar['y'], $bar['width'], $bar['height']);
+
+ if($this->getOption('show_tooltips'))
+ $this->setTooltip($bar, $item, $dataset, $item->key, $item->value,
+ $label_shown);
+ if($this->getOption('show_context_menu'))
+ $this->setContextMenu($bar, $dataset, $item, $label_shown);
+
+ $round = max($this->getItemOption('bar_round', $dataset, $item), 0);
+ if($round > 0) {
+ // don't allow the round corner to be more than 1/2 bar width or height
+ $bar['rx'] = $bar['ry'] = min($round, $bar['width'] / 2, $bar['height'] / 2);
+ }
+
+ $bar_part = $this->element('rect', $bar);
+ return $this->getLink($item, $item->key, $bar_part);
+ }
+
+ /**
+ * Returns the space before a bar
+ */
+ protected function barSpace($bar_width)
+ {
+ return max(0, ($this->x_axes[$this->main_x_axis]->unit() - $bar_width) / 2);
+ }
+
+ /**
+ * Returns an array with x, y, width and height set
+ */
+ protected function barDimensions($item, $index, $start, $axis, $dataset)
+ {
+ $bar = [];
+ $bar_x = $this->barX($item, $index, $bar, $axis, $dataset);
+ if($bar_x === null)
+ return [];
+
+ $y_pos = $this->barY($item->value, $bar, $start, $axis);
+ if($y_pos === null)
+ return [];
+ return $bar;
+ }
+
+ /**
+ * Fills in the x and width of bar
+ */
+ protected function barX($item, $index, &$bar, $axis, $dataset)
+ {
+ $bar_x = $this->gridPosition($item, $index);
+ if($bar_x === null)
+ return null;
+
+ $bar['x'] = $bar_x + $this->calculated_bar_space;
+ $bar['width'] = $this->calculated_bar_width;
+ return $bar_x;
+ }
+
+ /**
+ * Fills in the y and height of a bar
+ * @return number unclamped bar position
+ */
+ protected function barY($value, &$bar, $start = null, $axis = null)
+ {
+ if($start)
+ $value += $start;
+
+ $startpos = $start === null ? $this->originY($axis) :
+ $this->gridY($start, $axis);
+ if($startpos === null)
+ $startpos = $this->originY($axis);
+ $pos = $this->gridY($value, $axis);
+ if($pos === null) {
+ $bar['height'] = 0;
+ } else {
+ $l1 = $this->clampVertical($startpos);
+ $l2 = $this->clampVertical($pos);
+ $bar['y'] = min($l1, $l2);
+ $bar['height'] = abs($l1-$l2);
+ }
+ return $pos;
+ }
+
+ /**
+ * Override to check minimum space requirement
+ */
+ protected function addDataLabel($dataset, $index, &$element, &$item,
+ $x, $y, $w, $h, $content = null, $duplicate = true)
+ {
+ if($h < $this->getOption(['data_label_min_space', $dataset]))
+ return false;
+ return parent::addDataLabel($dataset, $index, $element, $item, $x, $y,
+ $w, $h, $content, $duplicate);
+ }
+
+ /**
+ * Returns the position for a data label
+ */
+ public function dataLabelPosition($dataset, $index, &$item, $x, $y, $w, $h,
+ $label_w, $label_h)
+ {
+ list($pos,$end) = parent::dataLabelPosition($dataset, $index, $item,
+ $x, $y, $w, $h, $label_w, $label_h);
+ $bpos = $this->getOption('bar_label_position');
+ if(!empty($bpos))
+ $pos = $bpos;
+
+ if($label_h > $h && Graph::isPositionInside($pos))
+ $pos = str_replace(['top','middle','bottom'], 'outside top inside ', $pos);
+
+ // flip top/bottom for negative values
+ if($item !== null && $item->value < 0) {
+ if(strpos($pos, 'top') !== false)
+ $pos = str_replace('top','bottom', $pos);
+ elseif(strpos($pos, 'above') !== false)
+ $pos = str_replace('above','below', $pos);
+ elseif(strpos($pos, 'below') !== false)
+ $pos = str_replace('below','above', $pos);
+ elseif(strpos($pos, 'bottom') !== false)
+ $pos = str_replace('bottom','top', $pos);
+ }
+
+ return [$pos, $end];
+ }
+
+ /**
+ * Returns the style options for bar labels
+ */
+ public function dataLabelStyle($dataset, $index, &$item)
+ {
+ $style = parent::dataLabelStyle($dataset, $index, $item);
+
+ // bar label settings can override global settings
+ $opts = [
+ 'font' => 'bar_label_font',
+ 'font_size' => 'bar_label_font_size',
+ 'font_weight' => 'bar_label_font_weight',
+ 'colour' => 'bar_label_colour',
+ 'altcolour' => 'bar_label_colour_above',
+ 'space' => 'bar_label_space',
+ ];
+ foreach($opts as $key => $opt)
+ if(isset($this->settings[$opt]))
+ $style[$key] = $this->settings[$opt];
+ return $style;
+ }
+
+ /**
+ * Return box for legend
+ */
+ public function drawLegendEntry($x, $y, $w, $h, $entry)
+ {
+ $bar = ['x' => $x, 'y' => $y, 'width' => $w, 'height' => $h];
+ return $this->element('rect', $bar, $entry->style);
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/BestFit.php b/classes/vendor/81x/goat1000/svggraph/BestFit.php
new file mode 100644
index 0000000..c9b762a
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/BestFit.php
@@ -0,0 +1,207 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * Class for drawing best-fit lines
+ */
+class BestFit {
+
+ protected $graph;
+ protected $bbox;
+ protected $lines_below = [];
+ protected $lines_above = [];
+
+ public function __construct(Graph &$graph, BoundingBox $bbox)
+ {
+ $this->graph =& $graph;
+ $this->bbox = $bbox;
+ }
+
+ /**
+ * Adds a line
+ */
+ public function add($dataset, $points)
+ {
+ $type = $this->graph->getOption(['best_fit', $dataset]);
+ if($type !== 'straight' && $type !== 'curve')
+ return;
+
+ // range and projection
+ $r_start = $r_end = $p_start = $p_end = null;
+ list($start, $end) = $this->getRange($dataset);
+
+ if($start !== null || $end !== null) {
+ if($start !== null)
+ $r_start = $this->graph->unitsX($start);
+ if($end !== null)
+ $r_end = $this->graph->unitsX($end);
+ $project = $this->graph->getOption(['best_fit_project', $dataset]);
+ $p_start = $project == 'start' || $project == 'both';
+ $p_end = $project == 'end' || $project == 'both';
+ $project = $p_start || $p_end;
+
+ $points = $this->filterPoints($points, $r_start, $r_end);
+ }
+
+ $class = '\\Goat1000\\SVGGraph\\' . ($type === 'straight' ? 'BestFitLine' : 'BestFitCurve');
+ $subtypes = $this->graph->getOption(['best_fit_type', $dataset]);
+
+ $best_fit = new $class($this->graph, $points, $subtypes);
+ $best_fit->calculate($this->bbox, $r_start, $r_end, $p_start, $p_end);
+ if($this->graph->getOption(['best_fit_above', $dataset]))
+ $this->lines_above[$dataset] = $best_fit;
+ else
+ $this->lines_below[$dataset] = $best_fit;
+ }
+
+ /**
+ * Returns the start and end of selection range
+ */
+ protected function getRange($dataset)
+ {
+ $range = $this->graph->getOption(['best_fit_range', $dataset]);
+ if(!is_array($range))
+ $range = $this->graph->getOption('best_fit_range');
+ if(!is_array($range))
+ return [null, null];
+ if(count($range) !== 2)
+ throw new \Exception('Best fit range must contain start and end values');
+ if($range[0] !== null && !is_numeric($range[0]))
+ throw new \Exception('Best fit range start not numeric or NULL');
+ if($range[1] !== null && !is_numeric($range[1]))
+ throw new \Exception('Best fit range end not numeric or NULL');
+ if($range[0] !== null && $range[1] !== null && $range[1] <= $range[0])
+ throw new \Exception('Best fit range start >= end');
+ return $range;
+ }
+
+ /**
+ * Filters out points outside the range
+ */
+ protected function filterPoints($points, $start, $end)
+ {
+ if($start === null && $end === null)
+ return $points;
+
+ if($start === null)
+ $callback = function($p) use ($end) { return $p->x <= $end; };
+ elseif($end === null)
+ $callback = function($p) use ($start) { return $p->x >= $start; };
+ else
+ $callback = function($p) use ($start, $end) { return $p->x <= $end && $p->x >= $start; };
+
+ return array_filter($points, $callback);
+ }
+
+ /**
+ * Returns the lines that go above the graph
+ */
+ public function getAbove()
+ {
+ return $this->getLines('lines_above');
+ }
+
+ /**
+ * Returns the lines below the graph
+ */
+ public function getBelow()
+ {
+ return $this->getLines('lines_below');
+ }
+
+ /**
+ * Creates the markup for a group of lines
+ */
+ private function getLines($which_lines)
+ {
+ $lines = '';
+ foreach($this->{$which_lines} as $dataset => $best_fit) {
+ $line_path = $best_fit->getLine();
+ if($line_path->isEmpty())
+ continue;
+
+ $proj_path = $best_fit->getProjection();
+ $lines .= $this->getLinePath($dataset, $line_path, $proj_path);
+ }
+ if($lines == '')
+ return $lines;
+
+ if($this->graph->getOption('semantic_classes')) {
+ $cls = ['class' => 'bestfit'];
+ $lines = $this->graph->element('g', $cls, null, $lines);
+ }
+ return $lines;
+ }
+
+ /**
+ * Wraps up the PathData with SVG
+ */
+ protected function getLinePath($dataset, $line_path, $proj_path)
+ {
+ // use ColourGroup to support fill and fillColour
+ $cg = new ColourGroup($this->graph, null, 0, $dataset, 'best_fit_colour');
+ $colour = $cg->stroke();
+ $stroke_width = $this->graph->getOption(['best_fit_width', $dataset]);
+ $dash = $this->graph->getOption(['best_fit_dash', $dataset]);
+ $opacity = $this->graph->getOption(['best_fit_opacity', $dataset]);
+ $above = $this->graph->getOption(['best_fit_above', $dataset]);
+ $type = $this->graph->getOption(['best_fit', $dataset]);
+ $path = [
+ 'd' => $line_path,
+ 'stroke' => $colour->isNone() ? '#000' : $colour,
+ ];
+ if($stroke_width != 1 && $stroke_width > 0)
+ $path['stroke-width'] = $stroke_width;
+ if(!empty($dash))
+ $path['stroke-dasharray'] = $dash;
+ if($opacity != 1)
+ $path['opacity'] = $opacity;
+ if($type != 'straight')
+ $path['fill'] = 'none'; // don't fill curves
+
+ $line = $this->graph->element('path', $path);
+ if($proj_path->isEmpty())
+ return $line;
+
+ // append the projection path
+ $path['d'] = $proj_path;
+ $cg = new ColourGroup($this->graph, null, 0, $dataset, 'best_fit_project_colour');
+ $colour = $cg->stroke();
+ $stroke_width = $this->graph->getOption(['best_fit_project_width', $dataset]);
+ $dash = $this->graph->getOption(['best_fit_project_dash', $dataset]);
+ $opacity = $this->graph->getOption(['best_fit_project_opacity', $dataset]);
+
+ if(!$colour->isNone())
+ $path['stroke'] = $colour;
+ if($stroke_width > 0)
+ $path['stroke-width'] = $stroke_width;
+ if(!empty($dash))
+ $path['stroke-dasharray'] = $dash;
+ if($opacity > 0)
+ $path['opacity'] = $opacity;
+
+ $line .= $this->graph->element('path', $path);
+ return $line;
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/BestFitCurve.php b/classes/vendor/81x/goat1000/svggraph/BestFitCurve.php
new file mode 100644
index 0000000..8e4005a
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/BestFitCurve.php
@@ -0,0 +1,245 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * Class for calculating a curved best-fit line
+ */
+class BestFitCurve {
+
+ protected $graph;
+ protected $points;
+ protected $line;
+ protected $projection;
+ protected $types;
+
+ public function __construct(&$graph, $points, $types = null)
+ {
+ $this->graph =& $graph;
+ $this->line = new PathData;
+ $this->projection = new PathData;
+ $this->points = $points;
+ $this->types = $types === null || is_array($types) ? $types : [$types];
+ }
+
+ /**
+ * Calculates the line and projection
+ */
+ public function calculate(BoundingBox $area, $limit_start, $limit_end,
+ $project_start, $project_end)
+ {
+ // can't draw a line through fewer than 2 points
+ $count = count($this->points);
+ if($count < 2)
+ return false;
+
+ $old_scale = bcscale(50);
+ $b = [];
+ $y = new Matrix($count, 1);
+ foreach($this->points as $p)
+ $b[] = $p->y;
+ $y->load($b);
+
+ $supported_types = ['straight', 'quadratic', 'cubic', 'quartic', 'quintic'];
+
+ // choose which functions to fit
+ if($this->types !== null) {
+ $types = [];
+ foreach($this->types as $type) {
+ if(!in_array($type, $supported_types))
+ throw new \Exception("Unknown curve type '{$type}'");
+ $types[] = $type;
+ }
+ } else {
+ $types = ['quintic'];
+ switch($count)
+ {
+ case 2 : $types = ['straight'];
+ break;
+ case 3 : $types = ['quadratic'];
+ break;
+ case 4 : $types = ['cubic'];
+ break;
+ case 5 : $types = ['quartic'];
+ }
+ }
+
+ // fit the functions, measure the error
+ $results = [];
+ $errors = [];
+ foreach($types as $t) {
+ $v = $this->vandermonde($t);
+ $result = $this->solve($v, $y);
+ if($result !== null) {
+ $errors[$t] = $this->error($v, $result);
+ $results[$t] = $result;
+ }
+ }
+
+ if(!empty($errors)) {
+
+ // sort by error, best first
+ uasort($errors, 'bccomp');
+ $best = null;
+ foreach($errors as $k => $v) {
+ $best = $k;
+ break;
+ }
+
+ $r = $results[$best];
+ $c = $r->asArray();
+
+ // plot the function
+ $fn = new Algebraic($best);
+ $fn->setCoefficients($c);
+ $this->buildPaths($fn, $area, $limit_start, $limit_end,
+ $project_start, $project_end);
+ }
+ bcscale($old_scale);
+ }
+
+ /**
+ * Calculates the error
+ */
+ protected function error(Matrix $v, Matrix $r)
+ {
+ $old_scale = bcscale(50);
+ $tr = $r->transpose();
+ $vr = $v->multiply($tr);
+ $i = 0;
+ $err = "0";
+ foreach($this->points as $p) {
+ $diff = bcsub($vr($i, 0), $p->y);
+ if(bccomp($diff, "0") == -1)
+ $err = bcsub($err, $diff);
+ else
+ $err = bcadd($err, $diff);
+ ++$i;
+ }
+ bcscale($old_scale);
+ return $err;
+ }
+
+ /**
+ * Solves the normal equation
+ */
+ protected function solve(Matrix $v, Matrix $y)
+ {
+ $v_t = $v->transpose();
+ $v_t_v = $v_t->multiply($v);
+ $v_t_y = $v_t->multiply($y);
+ return $v_t_v->gaussian_solve($v_t_y);
+ }
+
+ /**
+ * Returns the Vandermonde matrix for the curve type
+ */
+ protected function vandermonde($type)
+ {
+ $old_scale = bcscale(50);
+
+ // find size of matrix
+ $a = new Algebraic($type);
+ $test = $a->vandermonde(1);
+ $m = count($this->points);
+ $n = count($test) + 1;
+ $v = new Matrix($m, $n);
+
+ $i = 0;
+ foreach($this->points as $p)
+ {
+ $v($i, 0, 1);
+ $bcx = sprintf("%20.20F", $p->x);
+ $cols = $a->vandermonde($bcx);
+ foreach($cols as $k => $value)
+ $v($i, $k + 1, $value);
+ ++$i;
+ }
+ bcscale($old_scale);
+ return $v;
+ }
+
+ /**
+ * Builds the line and projection paths.
+ * For vertical lines, $slope = null and $y_int = $x
+ */
+ protected function buildPaths(&$fn, $area, $limit_start, $limit_end,
+ $project_start, $project_end)
+ {
+
+ // initialize min and max points of line
+ $x_min = $limit_start === null ? 0 : max($limit_start, 0);
+ $x_max = $limit_end === null ? $area->width() : min($limit_end, $area->width());
+ $y_min = 0;
+ $y_max = $area->height();
+ $line = new PathData;
+ $projection = new PathData;
+
+ $step = 1;
+ if($project_start)
+ $this->buildPath($projection, $fn, 0, $x_min, $y_min, $y_max, $step, $area);
+ $this->buildPath($line, $fn, $x_min, $x_max, $y_min, $y_max, $step, $area);
+ if($project_end)
+ $this->buildPath($projection, $fn, $x_max, $area->width(), $y_min, $y_max, $step, $area);
+
+ $this->projection = $projection;
+ $this->line = $line;
+ return true;
+ }
+
+ /**
+ * Builds a single path section between $x1 and $x2
+ */
+ private function buildPath(&$path, &$fn, $x1, $x2, $y_min, $y_max, $step, $area)
+ {
+ $cmd = 'M';
+ for($x = $x1; $x <= $x2; $x += $step) {
+ $y = $fn($x);
+ if($y < $y_min || $y > $y_max) {
+ $cmd = 'M';
+ continue;
+ }
+ $path->add($cmd, $area->x1 + $x, $area->y2 - $y);
+ switch($cmd) {
+ case 'M' : $cmd = 'L'; break;
+ case 'L' : $cmd = ''; break;
+ }
+ }
+ }
+
+ /**
+ * Returns the best-fit line as PathData
+ */
+ public function getLine()
+ {
+ return $this->line;
+ }
+
+ /**
+ * Returns the projection line(s) as PathData
+ */
+ public function getProjection()
+ {
+ return $this->projection;
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/BestFitLine.php b/classes/vendor/81x/goat1000/svggraph/BestFitLine.php
new file mode 100644
index 0000000..6c074d2
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/BestFitLine.php
@@ -0,0 +1,181 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * Class for calculating a best-fit line
+ */
+class BestFitLine {
+
+ protected $graph;
+ protected $points;
+ protected $line;
+ protected $projection;
+
+ public function __construct(&$graph, $points)
+ {
+ $this->graph =& $graph;
+ $this->line = new PathData;
+ $this->projection = new PathData;
+ $this->points = $points;
+ }
+
+ /**
+ * Calculates the line and projection
+ */
+ public function calculate(BoundingBox $area, $limit_start, $limit_end,
+ $project_start, $project_end)
+ {
+ // can't draw a line through fewer than 2 points
+ if(count($this->points) < 2)
+ return false;
+
+ $sum_x = $sum_y = $sum_x2 = $sum_xy = 0;
+ foreach($this->points as $p) {
+ $sum_x += $p->x;
+ $sum_y += $p->y;
+ $sum_x2 += pow($p->x, 2);
+ $sum_xy += $p->x * $p->y;
+ }
+
+ $mean_x = $sum_x / count($this->points);
+ $mean_y = $sum_y / count($this->points);
+
+ if($sum_x2 == $sum_x * $mean_x) {
+ // vertical line
+ $slope = null;
+ $y_int = $mean_x;
+ } else {
+ $slope = ($sum_xy - $sum_x * $mean_y) / ($sum_x2 - $sum_x * $mean_x);
+ $y_int = $mean_y - $slope * $mean_x;
+ }
+ $this->buildPaths($slope, $y_int, $area, $limit_start, $limit_end,
+ $project_start, $project_end);
+ }
+
+ /**
+ * Builds the line and projection paths.
+ * For vertical lines, $slope = null and $y_int = $x
+ */
+ protected function buildPaths($slope, $y_int, $area, $limit_start, $limit_end,
+ $project_start, $project_end)
+ {
+ // initialize min and max points of line
+ $x_min = $limit_start === null ? 0 : max($limit_start, 0);
+ $x_max = $limit_end === null ? $area->width() : min($limit_end, $area->width());
+ $y_min = 0;
+ $y_max = $area->height();
+ $line = new PathData;
+ $projection = new PathData;
+
+ if($slope === null) {
+ // line is vertical!
+ $coords = [
+ 'x1' => $y_int, 'x2' => $y_int,
+ 'y1' => $y_min, 'y2' => $y_max
+ ];
+ } else {
+ $coords = $this->boxLine($x_min, $x_max, $y_min, $y_max, $slope, $y_int);
+
+ if($project_end) {
+ $pcoords = $this->boxLine($coords['x2'], $area->width(), $y_min, $y_max,
+ $slope, $y_int);
+ if($pcoords !== null) {
+ $x1 = $pcoords['x1'] + $area->x1;
+ $x2 = $pcoords['x2'] + $area->x1;
+ $y1 = $area->y2 - $pcoords['y1'];
+ $y2 = $area->y2 - $pcoords['y2'];
+ $projection->add('M', $x1, $y1, 'L', $x2, $y2);
+ }
+ }
+ if($project_start) {
+ $pcoords = $this->boxLine(0, $coords['x1'], $y_min, $y_max,
+ $slope, $y_int);
+ if($pcoords !== null) {
+ $x1 = $pcoords['x1'] + $area->x1;
+ $x2 = $pcoords['x2'] + $area->x1;
+ $y1 = $area->y2 - $pcoords['y1'];
+ $y2 = $area->y2 - $pcoords['y2'];
+ $projection->add('M', $x1, $y1, 'L', $x2, $y2);
+ }
+ }
+ }
+ $x1 = $coords['x1'] + $area->x1;
+ $x2 = $coords['x2'] + $area->x1;
+ $y1 = $area->y2 - $coords['y1'];
+ $y2 = $area->y2 - $coords['y2'];
+ $line->add('M', $x1, $y1, 'L', $x2, $y2);
+
+ $this->projection = $projection;
+ $this->line = $line;
+ return true;
+ }
+
+ /**
+ * Returns the best-fit line as PathData
+ */
+ public function getLine()
+ {
+ return $this->line;
+ }
+
+ /**
+ * Returns the projection line(s) as PathData
+ */
+ public function getProjection()
+ {
+ return $this->projection;
+ }
+
+ /**
+ * Returns the coordinates of a line that passes through a box
+ */
+ public static function boxLine($x_min, $x_max, $y_min, $y_max, $slope, $y_int)
+ {
+ $x1 = $x_min;
+ $y1 = $slope * $x1 + $y_int;
+ $x2 = $x_max;
+ $y2 = $slope * $x2 + $y_int;
+
+ if($slope != 0) {
+ if($y1 < 0) {
+ $x1 = -$y_int / $slope;
+ $y1 = $y_min;
+ } elseif($y1 > $y_max) {
+ $x1 = ($y_max - $y_int) / $slope;
+ $y1 = $y_max;
+ }
+
+ if($y2 < 0) {
+ $x2 = - $y_int / $slope;
+ $y2 = $y_min;
+ } elseif($y2 > $y_max) {
+ $x2 = ($y_max - $y_int) / $slope;
+ $y2 = $y_max;
+ }
+ }
+ if($x1 == $x2 && $y1 == $y2)
+ return null;
+ return compact('x1','y1','x2','y2');
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/BoundingBox.php b/classes/vendor/81x/goat1000/svggraph/BoundingBox.php
new file mode 100644
index 0000000..7a75bb4
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/BoundingBox.php
@@ -0,0 +1,98 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * Class for measuring
+ */
+class BoundingBox {
+
+ public $x1, $x2, $y1, $y2;
+
+ public function __construct($x1, $y1, $x2, $y2)
+ {
+ $this->x1 = $x1;
+ $this->x2 = $x2;
+ $this->y1 = $y1;
+ $this->y2 = $y2;
+ }
+
+ /**
+ * Returns the width of the box
+ */
+ public function width()
+ {
+ return $this->x2 - $this->x1;
+ }
+
+ /**
+ * Returns the height of the box
+ */
+ public function height()
+ {
+ return $this->y2 - $this->y1;
+ }
+
+ /**
+ * Expands the box to fit the new sides
+ */
+ public function grow($x1, $y1, $x2, $y2)
+ {
+ $this->x1 = min($this->x1, $x1);
+ $this->y1 = min($this->y1, $y1);
+ $this->x2 = max($this->x2, $x2);
+ $this->y2 = max($this->y2, $y2);
+ }
+
+ /**
+ * Expands using another BoundingBox
+ */
+ public function growBox(BoundingBox $box)
+ {
+ $this->x1 = min($this->x1, $box->x1);
+ $this->y1 = min($this->y1, $box->y1);
+ $this->x2 = max($this->x2, $box->x2);
+ $this->y2 = max($this->y2, $box->y2);
+ }
+
+ /**
+ * Moves the box by $x, $y
+ */
+ public function offset($x, $y)
+ {
+ $this->x1 += $x;
+ $this->y1 += $y;
+ $this->x2 += $x;
+ $this->y2 += $y;
+ }
+
+ /**
+ * Flips the Y-axis values
+ */
+ public function flipY()
+ {
+ $tmp = $this->y1;
+ $this->y1 = -$this->y2;
+ $this->y2 = -$tmp;
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/BoxAndWhiskerGraph.php b/classes/vendor/81x/goat1000/svggraph/BoxAndWhiskerGraph.php
new file mode 100644
index 0000000..f7c6457
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/BoxAndWhiskerGraph.php
@@ -0,0 +1,283 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class BoxAndWhiskerGraph extends PointGraph {
+
+ private $min_value = null;
+ private $max_value = null;
+
+ public function __construct($w, $h, array $settings, array $fixed_settings = [])
+ {
+ $fs = [
+ 'label_centre' => !isset($settings['datetime_keys']),
+ 'require_structured' => ['top', 'bottom', 'wtop', 'wbottom'],
+ ];
+ $fs = array_merge($fs, $fixed_settings);
+ parent::__construct($w, $h, $settings, $fs);
+ }
+
+ protected function draw()
+ {
+ $body = $this->grid() . $this->underShapes();
+
+ $bar_width = $this->barWidth();
+ $dataset = $this->getOption(['dataset', 0], 0);
+ $x_axis = $this->x_axes[$this->main_x_axis];
+
+ $bspace = max(0, ($this->x_axes[$this->main_x_axis]->unit() - $bar_width) / 2);
+ $whisker_width = $this->getOption('whisker_width');
+ $whisker_dash = $this->getOption('whisker_dash');
+ $median_width = $this->getOption('median_stroke_width');
+ $median_dash = $this->getOption('median_dash');
+ $median_colour = $this->getOption('median_colour');
+ $bnum = 0;
+ $series = '';
+ foreach($this->values[$dataset] as $item) {
+ $bar_pos = $this->gridPosition($item, $bnum);
+
+ if($item->value !== null && $bar_pos !== null) {
+
+ $box_style = ['fill' => $this->getColour($item, $bnum, $dataset)];
+ $this->setStroke($box_style, $item, $bnum, $dataset);
+ $this->setLegendEntry($dataset, $bnum, $item, $box_style);
+
+ $top = $item->top;
+ $bottom = $item->bottom;
+ $round = max($this->getItemOption('bar_round', $dataset, $item), 0);
+ $m_colour = null;
+ if(!empty($median_colour)) {
+ $cg = new ColourGroup($this, $item, $bnum, $dataset, 'median_colour');
+ $m_colour = $cg->stroke();
+ }
+ $shape = $this->whiskerBox($bspace + $bar_pos, $bar_width, $item->value,
+ $top, $bottom, $item->wtop, $item->wbottom, $round, $whisker_width,
+ $whisker_dash, $median_width, $median_dash, $m_colour);
+
+ // wrap the whisker box in a group
+ $g = [];
+ $show_label = $this->addDataLabel($dataset, $bnum, $g, $item,
+ $bspace + $bar_pos, $this->gridY($top), $bar_width,
+ $this->gridY($bottom) - $this->gridY($top));
+ if($this->getOption('show_tooltips'))
+ $this->setTooltip($g, $item, $dataset, $item->key, $item->value, $show_label);
+ if($this->getOption('show_context_menu'))
+ $this->setContextMenu($g, $dataset, $item, $show_label);
+
+ if($this->getOption('semantic_classes'))
+ $g['class'] = 'series0';
+ $group = $this->element('g', array_merge($g, $box_style), null, $shape);
+ $series .= $this->getLink($item, $item->key, $group);
+
+ // add outliers as markers
+ $x = $bar_pos + $x_axis->unit() / 2;
+ foreach($this->getOutliers($item) as $ovalue) {
+ $y = $this->gridY($ovalue);
+ $this->addMarker($x, $y, $item);
+ }
+ }
+ ++$bnum;
+ }
+
+ $group = [];
+ if($this->getOption('semantic_classes'))
+ $group['class'] = 'series';
+ $shadow_id = $this->defs->getShadow();
+ if($shadow_id !== null)
+ $group['filter'] = 'url(#' . $shadow_id . ')';
+ if(!empty($group))
+ $series = $this->element('g', $group, null, $series);
+
+ $body .= $series;
+ $body .= $this->overShapes();
+ $body .= $this->axes();
+ $body .= $this->drawMarkers();
+ return $body;
+ }
+
+ /**
+ * Returns the width of a bar
+ */
+ protected function barWidth()
+ {
+ $bw = $this->getOption('bar_width');
+ if(is_numeric($bw) && $bw >= 1)
+ return $bw;
+ $unit_w = $this->x_axes[$this->main_x_axis]->unit();
+ $bw = $unit_w - $this->getOption('bar_space');
+ return max(1, $bw, $this->getOption('bar_width_min'));
+ }
+
+ /**
+ * Returns the code for a box with whiskers
+ */
+ protected function whiskerBox($x, $w, $median, $top, $bottom,
+ $wtop, $wbottom, $round, $whisker_width, $whisker_dash,
+ $median_width, $median_dash, Colour $median_colour = null)
+ {
+ $t = $this->gridY($top);
+ $b = $this->gridY($bottom);
+ $wt = $this->gridY($wtop);
+ $wb = $this->gridY($wbottom);
+
+ $box = ['x' => $x, 'y' => $t, 'width' => $w, 'height' => $b - $t];
+ if($round > 0) {
+ $box['rx'] = $box['ry'] = min($round, $box['width'] / 2,
+ $box['height'] / 2);
+ }
+ $rect = $this->element('rect', $box);
+
+ // whisker lines
+ $lg = $w * (1 - $whisker_width) * 0.5;
+ $ll = $x + $lg;
+ $lr = $x + $w - $lg;
+ $l = ['x1' => $ll, 'x2' => $lr];
+ $l['y1'] = $l['y2'] = $wt;
+ $l1 = $this->element('line', $l);
+ $l['y1'] = $l['y2'] = $wb;
+ $l2 = $this->element('line', $l);
+
+ // median line
+ $l['x1'] = $x;
+ $l['x2'] = $x + $w;
+ $l['y1'] = $l['y2'] = $this->gridY($median);
+ $style = [ 'stroke-width' => $median_width ];
+ if($median_colour !== null)
+ $style['stroke'] = $median_colour;
+ if(!empty($median_dash))
+ $style['stroke-dasharray'] = $median_dash;
+ $l3 = $this->element('line', array_merge($l, $style));
+
+ // whisker dashed lines
+ $style = [ 'stroke-dasharray' => $whisker_dash ];
+ $l['x1'] = $l['x2'] = $x + $w / 2;
+ $l['y1'] = $wt;
+ $l['y2'] = $t;
+ $w1 = $this->element('line', array_merge($l, $style));
+ $l['y1'] = $wb;
+ $l['y2'] = $b;
+ $w2 = $this->element('line', array_merge($l, $style));
+
+ return $rect . $w1 . $w2 . $l1 . $l2 . $l3;
+ }
+
+ /**
+ * Checks that the data contains sensible values
+ */
+ protected function checkValues()
+ {
+ parent::checkValues();
+
+ foreach($this->values[0] as $item) {
+ $val = $item->value;
+ if($val === null)
+ continue;
+ $wb = $item->wbottom;
+ $wt = $item->wtop;
+ $b = $item->bottom;
+ $t = $item->top;
+ if($wb > $b || $wt < $t || $val < $b || $val > $t) {
+
+ $wb = new Number($wb);
+ $b = new Number($b);
+ $wt = new Number($wt);
+ $t = new Number($t);
+ $val = new Number($val);
+ throw new \Exception('Data problem: ' . $wb . '--[' . $b . ' ' . $val .
+ ' ' . $t . ']--' . $wt);
+ }
+ }
+ }
+
+ /**
+ * Return box for legend
+ */
+ public function drawLegendEntry($x, $y, $w, $h, $entry)
+ {
+ $box = ['x' => $x, 'y' => $y, 'width' => $w, 'height' => $h];
+ return $this->element('rect', $box, $entry->style);
+ }
+
+ /**
+ * Returns the maximum bar end
+ */
+ public function getMaxValue()
+ {
+ if($this->max_value !== null)
+ return $this->max_value;
+ $max = null;
+ $dataset = $this->getOption(['dataset', 0], 0);
+ foreach($this->values[$dataset] as $item) {
+ if($item->value === null)
+ continue;
+ $points = [$item->wtop];
+ $points = array_merge($points, $this->getOutliers($item));
+ $m = max($points);
+ if($max === null || $m > $max)
+ $max = $m;
+ }
+ return ($this->max_value = $max);
+ }
+
+ /**
+ * Returns the minimum bar end
+ */
+ public function getMinValue()
+ {
+ if($this->min_value !== null)
+ return $this->min_value;
+ $min = null;
+ $dataset = $this->getOption(['dataset', 0], 0);
+ foreach($this->values[$dataset] as $item) {
+ if($item->value === null)
+ continue;
+ $points = [$item->wbottom];
+ $points = array_merge($points, $this->getOutliers($item));
+ $m = min($points);
+ if($min === null || $m < $min)
+ $min = $m;
+ }
+ return ($this->min_value = $min);
+ }
+
+ /**
+ * Returns the list of outliers for an item
+ */
+ protected function getOutliers(&$item)
+ {
+ $outliers = [];
+ $structure = $this->getOption('structure');
+ if(!isset($structure['outliers']) ||
+ !is_array($structure['outliers']))
+ return $outliers;
+
+ $min = $item->wbottom;
+ $max = $item->wtop;
+ foreach($structure['outliers'] as $o) {
+ $v = $item->rawData($o);
+ if($v !== null && ($v > $max || $v < $min))
+ $outliers[] = $v;
+ }
+ return $outliers;
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/BubbleGraph.php b/classes/vendor/81x/goat1000/svggraph/BubbleGraph.php
new file mode 100644
index 0000000..e4934bb
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/BubbleGraph.php
@@ -0,0 +1,134 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * BubbleGraph - scatter graph with bubbles instead of markers
+ */
+class BubbleGraph extends PointGraph {
+
+ public function __construct($w, $h, array $settings, array $fixed_settings = [])
+ {
+ $fs = [
+ 'repeated_keys' => 'accept',
+ 'require_integer_keys' => false,
+ 'require_structured' => ['area'],
+ ];
+ $fs = array_merge($fs, $fixed_settings);
+ parent::__construct($w, $h, $settings, $fs);
+ }
+
+ protected function draw()
+ {
+ $body = $this->grid() . $this->underShapes();
+ $dataset = $this->getOption(['dataset', 0], 0);
+
+ $bnum = 0;
+ $y_axis = $this->y_axes[$this->main_y_axis];
+ $series = '';
+
+ foreach($this->values[$dataset] as $item) {
+ $area = $item->area;
+ $x = $this->gridPosition($item, $bnum);
+ $y = null;
+ if($item->value !== null && $x !== null)
+ $y = $this->gridY($item->value);
+
+ if($y !== null) {
+ $r = $this->getOption('bubble_scale') * $y_axis->unit() * sqrt(abs($area) / M_PI);
+ $circle = ['cx' => $x, 'cy' => $y, 'r' => $r];
+ $colour = $this->getColour($item, $bnum, $dataset);
+ $circle_style = ['fill' => $colour];
+ if($area < 0) {
+ // draw negative bubbles with a checked pattern
+ $pattern = [$colour, 'pattern' => 'check', 'size' => 8];
+ $pid = $this->defs->addPattern($pattern);
+ $circle_style['fill'] = 'url(#' . $pid . ')';
+ }
+ $this->setStroke($circle_style, $item, $bnum, $dataset);
+ $this->addDataLabel($dataset, $bnum, $circle, $item,
+ $x - $r, $y - $r, $r * 2, $r * 2);
+
+ if($this->getOption('show_tooltips'))
+ $this->setTooltip($circle, $item, $dataset, $item->key, $area, true);
+ if($this->getOption('show_context_menu'))
+ $this->setContextMenu($circle, $dataset, $item, true);
+ if($this->getOption('semantic_classes'))
+ $circle['class'] = 'series0';
+ $bubble = $this->element('circle', array_merge($circle, $circle_style));
+ $series .= $this->getLink($item, $item->key, $bubble);
+
+ $this->addMarker($x, $y, $item, null, $dataset, false);
+ $this->setLegendEntry($dataset, $bnum, $item, $circle_style);
+ }
+
+ ++$bnum;
+ }
+
+ $group = [];
+ if($this->getOption('semantic_classes'))
+ $group['class'] = 'series';
+ $shadow_id = $this->defs->getShadow();
+ if($shadow_id !== null)
+ $group['filter'] = 'url(#' . $shadow_id . ')';
+ if(!empty($group))
+ $series = $this->element('g', $group, null, $series);
+
+ list($best_fit_above, $best_fit_below) = $this->bestFitLines();
+ $body .= $best_fit_below;
+ $body .= $series;
+ $body .= $this->overShapes();
+ $body .= $this->axes();
+ $body .= $this->drawMarkers();
+ $body .= $best_fit_above;
+ return $body;
+ }
+
+ /**
+ * Checks that the data produces a 2-D plot
+ */
+ protected function checkValues()
+ {
+ parent::checkValues();
+
+ // using force_assoc makes things work properly
+ if($this->values->associativeKeys())
+ $this->setOption('force_assoc', true);
+
+ // prevent drawing actual markers
+ $this->setOption('marker_size', 0);
+ }
+
+ /**
+ * Return bubble for legend
+ */
+ public function drawLegendEntry($x, $y, $w, $h, $entry)
+ {
+ $bubble = [
+ 'cx' => $x + $w / 2,
+ 'cy' => $y + $h / 2,
+ 'r' => min($w, $h) / 2
+ ];
+ return $this->element('circle', array_merge($bubble, $entry->style));
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/CHANGES.txt b/classes/vendor/81x/goat1000/svggraph/CHANGES.txt
new file mode 100644
index 0000000..3fc24c1
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/CHANGES.txt
@@ -0,0 +1,611 @@
+Version 3.20 (24/04/2023)
+------------
+- Added support for curved best-fit lines.
+- Added support for CSS units in font sizes and line spacing.
+
+Version 3.19 (10/01/2023)
+------------
+- Added ParetoChart.
+- Added fill_under "full" option to always fill to bottom of grid.
+- Updated code to work properly under PHP 8.2.
+
+Version 3.18 (02/12/2022)
+------------
+- Added shading of flat 3D pie graph sides.
+- Added support for colouring data labels using "fill" and "fillColour".
+- Fixed axis_text structure option on multi-dataset graphs.
+- Fixed explode "all" doing nothing on pie graphs with all values equal.
+
+Version 3.17 (16/09/2022)
+------------
+- Added axis_tightness_x option for removing space at ends of X-axis.
+- Added data_label_align option for label text alignment.
+- Added tooltip_align option for tooltip text alignment.
+- Fixed missing final bar when using axis_ticks_x with bar graphs.
+- Fixed data labels from structured data not showing when filter set to "none".
+
+Version 3.16 (29/07/2022)
+------------
+- Added support for multiple task bars/milestones on Gantt chart rows.
+- Added axis_double_x and axis_double_y options for same axis both sides.
+- Added axis_pad_* options for adding space between axes and grid.
+- Added axis_extend_* options for extending ends of axes.
+- Fixed axis with levels on right side of horizontal graph.
+
+Version 3.15 (16/06/2022)
+------------
+- Added dependency arrow support to Gantt charts.
+- Added gantt_units option for tasks in hours and minutes.
+- Added grid_clip_overlap_* options for adjusting line graph clipping.
+
+Version 3.14 (02/05/2022)
+------------
+- Added Gantt chart.
+- Added automatic graph height option.
+- Added support for best-fit lines on bar and line graphs.
+- Added support for using coordinates for legend position.
+- Added block_position_markers option.
+
+Version 3.13.1 (11/03/2022)
+--------------
+- Fixed deprecation warning messages displayed by PHP 8.1.
+- Fixed problem with file encoding of Text class.
+- Improved accuracy of HSL to RGB conversion.
+
+Version 3.13 (14/02/2022)
+------------
+- Added curved line graph support with line_curve option.
+- Added horizontal 3D bar graph.
+- Added horizontal grouped 3D bar graph.
+- Added horizontal stacked 3D bar graph.
+- Added line_bar option for setting marker positions on bar and line graphs.
+- Cylinder graphs now support bar_top_overlay_* options.
+- Made conversion of multi-dataset data to structured more robust.
+
+Version 3.12 (10/12/2021)
+------------
+- Added exploded semi-donut graph.
+- Added 3D donut graph.
+- Added 3D semi-donut graph.
+- Added exploded 3D donut graph.
+- Added exploded 3D semi-donut graph.
+- Added candlestick graph.
+- Added log_axis_x and log_axis_x_base options with logarithmic X-axis support.
+
+Version 3.11 (18/11/2021)
+------------
+- Added exploded donut graph.
+- Added donut_slice_gap option.
+- Fixed problem in datetime axis labelling around DST changes.
+- Fixed incorrect shape sizes on datetime graph around DST changes.
+- Fixed positioning of axis labels on datetime bar graphs.
+
+Version 3.10 (19/07/2021)
+------------
+- Added support for using text as a shape or figure.
+- Fixed angled axis text being badly placed with new limit_text_angle option.
+- Fixed guidelines not showing on date/time graphs.
+- Fixed some missing date/time tokens in crosshair text.
+- Fixed histograms not supporting non-integer values.
+- Fixed polar area graphs drawing incorrectly with date/time keys.
+
+Version 3.9 (24/05/2021)
+-----------
+- Added support for linking legend entries and title.
+- Added axis_ticks_x and axis_ticks_y options for specific tick marks.
+- Added legend_autohide_opacity option.
+- Added autohide and autohide_opacity options for shapes.
+- Added grid stroke width options.
+
+Version 3.8 (23/02/2021)
+-----------
+- Added SteppedLineGraph and MultiSteppedLineGraph graph types.
+- Added force_block_label_x option.
+- Added axis_zero_y option.
+- Added data_label_opacity option.
+- Added various line spacing options.
+- Added localization of date strings.
+- Improved timezone support for date/time values.
+- Fixed axis label position on graphs with negative values.
+- Fixed date conversion problems in guideline positions.
+- Fixed empty legend entry on multi-line graphs with single point in dataset.
+
+Version 3.7 (27/11/2020)
+-----------
+- Added options for displaying mean average lines.
+- Added background shadow support.
+- Added graph subtitle options.
+- Added alternative Y-axis fitting algorithm with axis_tightness_y option.
+- Updated axis_label_position options to support aligning text with axis ends.
+- Updated colourRangeHex functions to support more colour options.
+
+Version 3.6 (19/06/2020)
+-----------
+- Added support for multi-level axis division labels.
+- Added axis_font_weight option and its horizontal and vertical options.
+- Added axis_text_back_colour option and horizontal, vertical options.
+- Fixed display of axis lines when using a gradient.
+- Fixed crosshairs not hiding when cursor leaves grid.
+- Fixed link_target and link_base options not working.
+
+Version 3.5 (07/03/2020)
+-----------
+- Added support for saturation, brightness and hue colour filters.
+- Added support for using fill colour or style in best-fit lines.
+- Added support for using fill colour or style in box and whisker median line.
+- Added support for using gradients in patterns.
+- Added legend_unique_fields option for preventing duplicate legend text.
+- Added legend_entries option callback for more control over legend entries.
+
+Version 3.4 (20/12/2019)
+-----------
+- Added SVG filter based shadows and options for adjusting them.
+- Added legend spacing and padding options.
+- Added support for stroking graph, tooltip, legend and label lines using
+ gradients and patterns.
+- Added support for stroking graph lines using fill colour or fill style.
+- Added support for filling markers with gradients and patterns.
+- Added support for displaying gradients in radar graph grid stripes.
+- Added simple Javascript minifier function.
+- Fixed 3D bar graphs having spiky corners at the top.
+- Fixed histogram X-axis being truncated.
+- Fixed 3D pie graph side shading drawing over edges.
+- Fixed lousy axis top end calculation when non-zero bottom end specified.
+- Fixed legend entries not showing for floating graph bars starting at zero.
+
+Version 3.3 (22/11/2019)
+-----------
+- Added ArrayGraph type for simple multiple-graph documents.
+- Added dataset option support to all graph types.
+- Added bar_round option for rounded corners on bar graphs.
+- Added median_colour and median_dash options for box and whisker graphs.
+
+Version 3.2 (26/09/2019)
+-----------
+- Added axis_label_position options.
+- Added legend_order option.
+- Added axis_fallback_max option for better handling of all-zero value graphs.
+- Added dataset option for choosing dataset for pie graphs to draw.
+- Fixed legend display for stacked bar and line graphs and population pyramid.
+- Fixed legend display for multi-dataset graphs starting with 0 values.
+- Fixed magnification of tooltips in subgraphs.
+- Fixed incorrect position of crosshairs when magnification enabled.
+- Fixed axis text when using force_assoc and value with a key of 0.
+- Fixed overlapped tooltips from internal edges of 3D polar area graphs.
+
+Version 3.1 (02/08/2019)
+-----------
+- Added subgraph support.
+- Added magnifier options.
+- Added support for using figures as patterns.
+
+Version 3.0.1 (14/06/2019)
+-------------
+- Added text formatting classes to prevent localised numbers in SVG attributes.
+- Fixed data label click show and fade on pages with multiple graphs.
+
+Version 3.0 (14/03/2019)
+-----------
+- All classes are now in Goat1000\SVGGraph\ namespace.
+- PSR-4 autoloader required for loading class files.
+- Added autoloader.php for anyone not using a framework with PSR-4 autoloader.
+- Values, colours and links must now be set using functions, not directly.
+- Added EmptyGraph type for using shapes and labels without data.
+- Added use_iconv and use_mbstring options.
+- Added line_breaks option for line graphs.
+- Split redundant code out into traits.
+- Removed compat_events code and option.
+- Fixed tooltips going off edge of small graphs on same page as larger graphs.
+- Fixed auto tail length for custom labels.
+- Fixed edge shading on 3D polar area graphs.
+
+Version 2.30 (04/01/2019)
+------------
+- Added "image" shape support.
+- Added "marker" shape support.
+- Added "figure" option for creating predefined shapes.
+- Added "figure" shape support.
+- Added "figure" marker support.
+- Improved axis label positioning.
+
+Version 2.29 (09/10/2018)
+------------
+- Added context menu options.
+- Added "box" division and subdivision style.
+- Added axis_text_location option for text position on graphs with negative
+ values.
+- Moved axis measurement and drawing out of GridGraph and subclasses into new
+ DisplayAxis classes.
+- Replaced GetFirst() and ArrayOption() functions with GetOption().
+
+Version 2.28 (23/07/2018)
+------------
+- Added text measurement class using font metrics and/or character widths.
+
+Version 2.27 (28/03/2018)
+------------
+- Added support for multiple Y-axes using dataset_axis option.
+- Added support for best-fit lines on bubble graphs.
+- Added line_figure and line_figure_closed options for drawing shapes on line
+ graphs.
+- Fixed crosshairs being left behind when cursor moved out of document.
+- Fixed broken bars when all values in stacked grouped 3D bar and cylinder
+ graphs negative.
+
+Version 2.26 (10/01/2018)
+------------
+- Added "auto" option for data label tail length.
+- Added data_label_same_size option for more consistent labels.
+- Setting data_label_round for line or plain labels now uses a circular
+ bounding box.
+- Added circle, square, linecircle, linebox, linesquare and line2 label styles.
+- Added tail end options for new label styles.
+- Added no_tspan option for compatibility with renderers that don't support
+ tspan elements fully.
+
+Version 2.25 (12/10/2017)
+------------
+- Added 3D polar area graph.
+- Added 3D exploded pie graph.
+- Added support for datetime keys in shapes and labels.
+- 3D pie graph sides are now drawn correctly when end_angle used.
+- Improved PHP7 compatibility.
+- Moved guideline code into separate file.
+- Fixed display of semi-donut graph when there is a single value.
+- Fixed display of multi-line axis text on horizontal graphs.
+- Fixed multi-line text alignment when minifying is disabled.
+- Fixed legend_show_empty behaviour inconsistencies.
+- Fixed incorrect legend size when containing numeric strings.
+- Fixed missing bar tops on stacked grouped 3D bar graphs.
+
+Version 2.24.1 (21/08/2017)
+--------------
+- Added stacked bar and line graph.
+- 0 values are now labelled on bar graphs.
+- Added "nonzero" data label filter to disable showing 0 value labels.
+- Added support for associative and datetime keys in X-axis guidelines.
+- Fixed axis text callback passing floating point values instead of integers
+ for array keys.
+
+Version 2.24 (31/01/2017)
+------------
+- Added semi-donut graph.
+- Added associative and datetime key support to shapes, labels and crosshairs.
+- Added end_angle and slice_fit options for drawing partial pie graphs.
+- Added keep_colour_order option for simpler pie graph slice colouring.
+- Added marker_opacity and marker_angle options.
+- Added custom marker support.
+- Made FetchJavascript work when called statically.
+- Fixed minify_js option to support anonymous callback functions.
+- Fixed error when array of guidelines contains a single guideline array.
+- Fixed bar_total_outline_colour option on horizontal stacked graphs.
+- Fixed javascript error in legend_autohide.
+- Fixed legend order for graphs with multiple datasets starting at different
+ key values.
+- Improved positioning of exploded pie slices.
+
+Version 2.23.1 (03/08/2016)
+--------------
+- Added datetime_key_format option.
+- Fixed per-dataset stroke_width with a 0 value.
+- Fixed default structure missing values with string keys when main key is 0.
+- Fixed error when using datetime keys with multiple unstructured datasets.
+- Fixed legend autohide not working when no other Javascript options enabled.
+- Fixed error in radar graphs when given some dual-Y axis settings.
+
+Version 2.23 (22/06/2016)
+------------
+- Added date/time axis.
+- Added callback function support to logarithmic axis.
+- Added exception_throw option to disable catching exceptions.
+- Fixed axis callback functions not working with associative data.
+- Fixed legend display for line graphs when marker size is 0.
+
+Version 2.22 (05/05/2016)
+------------
+- Added support for legend entries from structured data.
+- Added show_legend option (default true) for disabling legend.
+- Added support for multiple lines of text in legend entries and title.
+- Added support for using coordinates from second Y-axis in shapes and labels.
+- Fixed hex and decimal entities being escaped in text.
+
+Version 2.21 (17/03/2016)
+------------
+- Added best-fit line support to line graphs.
+- Added best_fit_range option for limiting best-fit line.
+- Added best_fit_project options for projecting beyond best-fit range.
+- Merged code to allow repeated keys in pie graphs.
+- Fixed displaying image markers with absolute links.
+- Fixed minimum fallback bar width to 1 pixel.
+
+Version 2.20.1 (19/01/2016)
+--------------
+- Added bar_width_min option.
+- Added clip_to_grid option for shapes.
+- Fixed shape units calculations when axes not starting in bottom left.
+- Fixed error when gradient array contains a single colour.
+
+Version 2.20 (05/12/2015)
+------------
+- Added stacked grouped 3D bar graph and stacked grouped cylinder graph.
+- Added shape options.
+- Added custom label options.
+- Added svg_class option.
+- Added grid_back_opacity and grid_back_stripe_opacity options.
+- Added multiple colour stripe support to grid_back_stripe_colour option.
+- Added gradient stop support to gradients.
+- Added radial gradient support for radar graph backgrounds.
+- Fixed pattern definitions not being reused.
+- Fixed javascript crosshair coordinates jumping around.
+- Fixed problems with autoloading graph classes.
+
+Version 2.19.1 (21/08/2015)
+--------------
+- Added bar_total_callback option.
+- Fixed stacked grouped bar graph height calculations.
+- Fixed structured_data to disabled structure when set to FALSE.
+
+Version 2.19 (17/07/2015)
+------------
+- Added histogram.
+- Added shading overlay for 3D bar sides and top.
+- Added best_fit_opacity and best_fit_above options.
+- Added semantic class for best fit lines.
+- Added tooltip and data label callback options.
+- Added data_label_min_space option.
+
+Version 2.18 (03/04/2015)
+------------
+- Added bar and line graph.
+- Added data label options for all graph types.
+- Added semantic_classes option.
+- Added axis text callback options.
+- Added detection of mbstring extension. If not available, fall back to basic
+ string functions.
+- Added check for too many divisions being created.
+- Fixed broken axis_right option on some multi-dataset graphs.
+- Fixed legend dragging.
+- Fixed divide by zero error in colour range function.
+
+Version 2.17 (27/11/2014)
+------------
+- Added fixed axis positioning options.
+- Added colour range and set functions.
+- Added auto_fit option and resizing support.
+- Fixed crosshairs bugs when embedded in HTML.
+
+Version 2.16 (24/09/2014)
+------------
+- Added stacked grouped bar graph.
+- Added image markers support.
+- Added skewing of 3D bar side and top.
+- Added support for fixed bar width.
+- Added option to set the number of decimal digits shown on axis text.
+- Added opacity value support for graph background colour.
+- Added exception details option for debugging.
+- Replaced all @ error handlers with tests for problem values.
+- Fixed escaping of text.
+- Fixed blank lines in multi-line text being ignored.
+- Fixed rendering of graphs with a single value.
+
+Version 2.15.1 (12/05/2014)
+--------------
+- Added crosshair options.
+- Fixed X axis being incorrectly labelled when using large associative dataset.
+
+Version 2.15 (11/03/2014)
+------------
+- Added donut graph.
+- Added polar area graph.
+- Added exploded pie graph.
+- Added support for dual Y axes.
+- Fixed legend dragging when embedded in HTML.
+
+Version 2.14 (05/10/2013)
+------------
+- Added pattern fill support.
+- Added total labels for stacked bar graphs.
+- Better validation of structured data.
+- Better handling of non-ASCII strings.
+- Replaced rgba() values with opacity attributes.
+- Fixed compatibility with PHP 5.1.x.
+- Fixed drawing of pie graphs containing relatively small values.
+- Fixed X axis calculations for negative or non-integral values.
+- Fixed structured axis_text when units_label or units_before_label used.
+- Fixed position of labels on stacked bar graphs with negative values.
+- Fixed legend entries skipping NULL or 0 values.
+
+Version 2.13 (31/07/2013)
+------------
+- Added PopulationPyramid graph.
+- Added units_before_x and units_before_y options for axis text.
+- Added units_label and units_before_label options for pie/bar labels.
+- Added units_tooltip, units_before_tooltip, units_tooltip_key and
+ units_before_tooltip_key options for tooltip values.
+- Added stroke_dash, minify and minify_js options.
+- Added structured data options: marker_type, marker_size, marker_stroke_width,
+ marker_stroke_colour, stroke_colour, stroke_width and stroke_dash.
+- Fixed display of character entities in tooltips.
+- Fixed measurement of text containing character entities.
+- Fixed dragging of legend over pie graphs.
+
+Version 2.12 (18/05/2013)
+------------
+- Added log_axis_y and log_axis_y_base options.
+- Added decimal and thousands options for number formatting.
+- Added grid_back_stripe and grid_back_stripe_colour options.
+- Added per-axis font options.
+- Added units_x and units_y options for axis text.
+- Added show_label_key option for pie graphs.
+- 3D pie graphs now calculate a depth when the setting is too great.
+- Fixed structure option to take precedence over scatter_2d option.
+- Fixed display of small numbers on axes.
+- Added workaround for Safari 6 bug.
+
+Version 2.11 (12/03/2013)
+------------
+- Added stacked and grouped cylinder graphs.
+- Added structured data support.
+- Added normal and horizontal floating bar graphs.
+- Added bubble graph.
+- Added box and whisker graph.
+- Added back_round_clip option.
+- Added reverse option for radar graphs.
+- Added support for negative values on radar graphs.
+- Added support for multi-line tooltips.
+- Fixed drawing of axes when all values negative.
+- Fixed svg node in namespaced XHTML.
+- Improved performance.
+
+Version 2.10 (23/10/2012)
+------------
+- Added cylinder graph.
+- Added stacked and grouped 3D bar graphs.
+- Added support for gradients in graph element backgrounds.
+- Added gradient shaded side to 3D pie graphs.
+- Added best fit lines for scatter graphs.
+- Added support for drawing associative data on scatter graphs.
+- Added show_axis_h and show_axis_v options.
+- Added minimum_units_y option.
+- Improved axis text label spacing again.
+- Better text positioning for radar graphs.
+- New default colours.
+- Fixed tooltips positioning when embedded in HTML5/XHTML.
+- Fixed text being selected when dragging legend.
+- Fixed graphs with all values negative not being drawn.
+
+Version 2.9 (29/08/2012)
+-----------
+- Added bar label options.
+- Added axis division and subdivision styles and other options.
+- Added diamond, hexagon, octagon, asterisk, star, threestar, fourstar and
+ eightstar markers.
+- Added legend columns option.
+- Added guide line opacity, text opacity, font adjust, text align, length and
+ length in units options.
+- Added grid background colour option.
+- Added grid line style options.
+- Added marker border options.
+- Added character encoding option.
+- Added gradient stop opacity support.
+- Improved subdivision calculations.
+- Improved axis text label spacing.
+- Removed main clipping path when not using a rounded rectangle background.
+- Fixed grid clipping path using non-unique ID.
+- Fixed line graphs not reaching right hand side of graph.
+- Fixed line graphs drawing vertical line to axis when filling enabled.
+- Fixed guide line title line spacing.
+- Fixed error caused by empty data sets.
+
+Version 2.8 (27/07/2012)
+-----------
+- Added stacked line graph.
+- Added radar graph and multi-radar graph.
+- Added axis_text_space option.
+- Added axis_stroke_width option.
+- Added force_assoc option.
+- Fixed legend dragging without tooltips enabled.
+- Fixed display of labels when axes disabled.
+- Fixed use of associative array data.
+- Added fill to legend display for line graphs.
+
+Version 2.7.1 (02/07/2012)
+-------------
+- Fixed order of entries in multi-line graphs.
+- Fixed script type attribute to match SVG standard.
+- Fixed order of legend entries for stacked bar, horizontal bar and
+ horizontal grouped bar graphs.
+- Fixed format of negative decimal numbers.
+
+Version 2.7 (25/05/2012)
+----------
+- Added guideline options.
+- Added axis text rotation.
+- Improved HTML embedding.
+- Reduced output size of grouped and stacked bar graphs.
+
+Version 2.6 (30/03/2012)
+-----------
+- Moved all configuration defaults to svggraph.ini.
+- Added legend_* options.
+- Added label_* options.
+- Added graph_title_* options.
+
+Version 2.5.1 (20/02/2012)
+-------------
+- Fixed bug with 0 values in stacked graphs.
+
+Version 2.5 (09/08/2011)
+-----------
+- Improved Javascript event handlers.
+- Added grid and axis subdivision options.
+- Added stroke width option.
+- Added more line/scatter marker symbols.
+- Added line dash option.
+- Added support for per-dataset fill opacity and line width.
+- Added pie graph start angle option.
+
+Version 2.4 (07/07/2011)
+-----------
+- Added multiple scatter graph.
+- Added option to use full x,y data for scatter graphs.
+- Added support for per-dataset marker options.
+- Added support for per-dataset fill_under options.
+- Fixed axis options are now available on both axes.
+
+Version 2.3 (26/04/2011)
+-----------
+- Added horizontal bar graph.
+- Added horizontal stacked bar graph.
+- Added horizontal grouped bar graph.
+- Updated 3D bar graph to support negative values.
+- Added fixed axis and division options.
+
+Version 2.2.1 (17/03/2011)
+-------------
+- Fixed display of graphs with a single data point.
+
+Version 2.2 (21/02/2011)
+-----------
+- Added background image support.
+- Added support for negative numbers on bar/line graphs.
+- Updated axis calculations.
+- Tooltips are now supported for all graph types.
+
+Version 2.1 (12/01/2011)
+-----------
+- Added stacked bar graph.
+- Added grouped bar graph.
+- Added multiple line graph.
+- Improved gradient support.
+- Made markers mandatory on scatter graph.
+
+Version 2.0 (19/08/2010)
+-----------
+- Update to PHP 5 OOP syntax.
+- Added 3D pie graph.
+- Added scatter graph.
+- Added tooltips for graph markers.
+- Minor improvements.
+
+Version 1.2.1 (11/04/2010)
+-------------
+- Fixed error shown with E_STRICT.
+
+Version 1.2 (01/05/2009)
+-----------
+- Added Bar3DGraph graph type.
+- Added axis divisions.
+
+Version 1.1 (27/03/2009)
+-----------
+- Added PieGraph graph type.
+- Added title and description options.
+- Added namespaced output option.
+- Reduced output file size using grouping, symbols.
+- Unused gradients are no longer output.
+
+Version 1.0 (16/03/2009)
+-----------
+- First released version.
+
diff --git a/classes/vendor/81x/goat1000/svggraph/COPYING b/classes/vendor/81x/goat1000/svggraph/COPYING
new file mode 100644
index 0000000..94a9ed0
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/COPYING
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/classes/vendor/81x/goat1000/svggraph/COPYING.LESSER b/classes/vendor/81x/goat1000/svggraph/COPYING.LESSER
new file mode 100644
index 0000000..fc8a5de
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/COPYING.LESSER
@@ -0,0 +1,165 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/classes/vendor/81x/goat1000/svggraph/CandlestickGraph.php b/classes/vendor/81x/goat1000/svggraph/CandlestickGraph.php
new file mode 100644
index 0000000..a15d774
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/CandlestickGraph.php
@@ -0,0 +1,206 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class CandlestickGraph extends BarGraph {
+
+ private $min_value = null;
+ private $max_value = null;
+
+ public function __construct($w, $h, array $settings, array $fixed_settings = [])
+ {
+ $fs = [
+ 'label_centre' => !isset($settings['datetime_keys']),
+ 'require_structured' => ['open', 'high', 'low'],
+ ];
+ $fs = array_merge($fs, $fixed_settings);
+ parent::__construct($w, $h, $settings, $fs);
+ }
+
+ /**
+ * Check that all the values are in the right order
+ */
+ protected function checkValues()
+ {
+ parent::checkValues();
+
+ $fields = ['low', 'high', 'open'];
+ foreach($this->values[0] as $item) {
+ $val = $item->value;
+ if($val === null)
+ continue;
+
+ foreach($fields as $f) {
+ if(!is_numeric($item->$f))
+ throw new \Exception("Data error: field '$f' is not numeric (key:'{$item->key}', value:'{$item->$f}')");
+ }
+
+ $wb = $item->low;
+ $wt = $item->high;
+ $o = $item->open;
+ $b = min($val, $o);
+ $t = max($val, $o);
+ if($wb > $b || $wt < $t) {
+
+ $wb = new Number($wb);
+ $b = new Number($b);
+ $wt = new Number($wt);
+ $t = new Number($t);
+ throw new \Exception('Data problem: ' . $wb . '--[' . $b . ' ' . $t . ']--' . $wt);
+ }
+ }
+ }
+
+ /**
+ * Returns the maximum bar end
+ */
+ public function getMaxValue()
+ {
+ if($this->max_value !== null)
+ return $this->max_value;
+ $max = null;
+ $dataset = $this->getOption(['dataset', 0], 0);
+ foreach($this->values[$dataset] as $item) {
+ if($item->value === null)
+ continue;
+ if($max === null || $item->high > $max)
+ $max = $item->high;
+ }
+ return ($this->max_value = $max);
+ }
+
+ /**
+ * Returns the minimum bar end
+ */
+ public function getMinValue()
+ {
+ if($this->min_value !== null)
+ return $this->min_value;
+ $min = null;
+ $dataset = $this->getOption(['dataset', 0], 0);
+ foreach($this->values[$dataset] as $item) {
+ if($item->value === null)
+ continue;
+ if($min === null || $item->low < $min)
+ $min = $item->low;
+ }
+ return ($this->min_value = $min);
+ }
+
+ /**
+ * Sets up the colours used for the graph
+ */
+ protected function setup()
+ {
+ $dataset = $this->getOption(['dataset', 0], 0);
+
+ // use two datasets for colours
+ $this->colourSetup($this->values->itemsCount($dataset), 2);
+ }
+
+ /**
+ * Returns an array with x, y, width and height set
+ */
+ protected function barDimensions($item, $index, $start, $axis, $dataset)
+ {
+ $bar = [];
+ $bar_x = $this->barX($item, $index, $bar, $axis, $dataset);
+ if($bar_x === null)
+ return [];
+
+ $start = $item->value;
+ $value = $item->open - $start;
+ $y_pos = $this->barY($value, $bar, $start, $axis);
+ if($y_pos === null)
+ return [];
+ return $bar;
+ }
+
+ /**
+ * Returns the SVG code for a bar
+ */
+ protected function drawBar(DataItem $item, $index, $start = 0, $axis = null,
+ $dataset = 0, $options = [])
+ {
+ if($item->value === null)
+ return '';
+
+ // negative bars are a different colour
+ $negative = $item->value < $item->open;
+ if($negative)
+ ++$dataset;
+
+ $bar = $this->barDimensions($item, $index, $start, $axis, $dataset);
+ if(empty($bar))
+ return '';
+
+ if($bar['height'] < 1)
+ $bar['height'] = 1;
+ $this->setStroke($bar, $item, $index, $dataset);
+ $bar['fill'] = $this->getColour($item, $index, $dataset);
+
+ if($this->getOption('semantic_classes'))
+ $bar['class'] = 'series' . $dataset;
+
+ $label_shown = $this->addDataLabel($dataset, $index, $bar, $item,
+ $bar['x'], $bar['y'], $bar['width'], $bar['height']);
+
+ if($this->getOption('show_tooltips'))
+ $this->setTooltip($bar, $item, $dataset, $item->key, $item->value,
+ $label_shown);
+ if($this->getOption('show_context_menu'))
+ $this->setContextMenu($bar, $dataset, $item, $label_shown);
+
+ $round = max($this->getItemOption('bar_round', $dataset, $item), 0);
+ if($round > 0) {
+ // don't allow the round corner to be more than 1/2 bar width or height
+ $bar['rx'] = $bar['ry'] = min($round, $bar['width'] / 2, $bar['height'] / 2);
+ }
+
+ // wick lines
+ $lx = $bar['x'] + ($bar['width'] / 2);
+ $ly1 = $this->gridY($item->high);
+ $ly2 = $bar['y'];
+ $ly3 = $ly2 + $bar['height'];
+ $ly4 = $this->gridY($item->low);
+
+ $wick_width = max(0.25, $this->getOption(['wick_stroke_width', $dataset], 1));
+ $wick_dash = $this->getOption(['wick_dash', $dataset]);
+ $style = [ 'stroke-width' => $wick_width ];
+ if(isset($bar['stroke']))
+ $style['stroke'] = $bar['stroke'];
+ if(!empty($wick_dash))
+ $style['stroke-dasharray'] = $wick_dash;
+
+ $l1 = $l2 = '';
+ if($ly1 != $ly2)
+ $l1 = $this->element('line', array_merge($style,
+ ['x1' => $lx, 'x2' => $lx, 'y1' => $ly1, 'y2' => $ly2]));
+ if($ly3 != $ly4)
+ $l2 = $this->element('line', array_merge($style,
+ ['x1' => $lx, 'x2' => $lx, 'y1' => $ly3, 'y2' => $ly4]));
+
+ $bar_part = $this->element('rect', $bar);
+ return $this->getLink($item, $item->key, $bar_part . $l1 . $l2);
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/Circle.php b/classes/vendor/81x/goat1000/svggraph/Circle.php
new file mode 100644
index 0000000..dde900a
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/Circle.php
@@ -0,0 +1,31 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class Circle extends Shape {
+ protected $element = 'circle';
+ protected $required = ['cx','cy','r'];
+ protected $transform = ['r' => 'y'];
+ protected $transform_from = ['r' => 'cy'];
+ protected $transform_pairs = [ ['cx', 'cy'] ];
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/Colour.php b/classes/vendor/81x/goat1000/svggraph/Colour.php
new file mode 100644
index 0000000..3714090
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/Colour.php
@@ -0,0 +1,239 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * Class for parsing colour/gradient/pattern
+ */
+class Colour {
+ private $graph;
+ private $colour = '#000';
+ private $opacity = 1;
+ private $as_string = '';
+ private $gradient = false;
+ private $pattern = false;
+ private $radial = false;
+ private $key = null;
+
+ public function __construct(&$graph, $colour, $allow_gradient = true,
+ $allow_pattern = true, $radial_gradient = false)
+ {
+ if($colour === null || $colour === 'none') {
+ $this->colour = $this->as_string = 'none';
+ return;
+ }
+
+ if(is_object($colour) && get_class($colour) === 'Goat1000\\SVGGraph\\Colour')
+ $colour = $colour->colour;
+
+ if(is_string($colour)) {
+ $this->extract($colour);
+ return;
+ }
+
+ // if not a string, must be an array with a colour at index 0
+ $valid = false;
+ if(is_array($colour)) {
+ if(is_string($colour[0])) {
+ $valid = true;
+ } elseif(isset($colour['pattern'])) {
+ // allow gradients as first colour of pattern
+ if(is_array($colour[0]) && count($colour[0]) > 1) {
+ $valid = true;
+ foreach($colour[0] as $i) {
+ if(!is_string($i))
+ $valid = false;
+ }
+ }
+ }
+ }
+
+ if(!$valid)
+ throw new \InvalidArgumentException('Malformed colour value: ' .
+ serialize($colour));
+
+ if(count($colour) < 2 || (!$allow_gradient && !$allow_pattern)) {
+ $this->extract($colour[0]);
+ return;
+ }
+
+ $this->graph =& $graph;
+ $this->colour = $colour;
+ if(isset($colour['pattern'])) {
+ if(!$allow_pattern)
+ $this->extract($colour[0]);
+ $this->pattern = true;
+ return;
+ }
+
+ if(!$allow_gradient) {
+ $this->extract($colour[0]);
+ return;
+ }
+
+ $err = array_diff_key($colour, array_keys(array_keys($colour)));
+ if($err)
+ throw new \InvalidArgumentException('Malformed gradient/pattern: ' .
+ serialize($colour));
+ $this->gradient = true;
+
+ $last = count($colour) - 1;
+ $this->radial = $radial_gradient || $colour[$last] == 'r';
+ }
+
+ /**
+ * Extract the colour and opacity into this instance
+ */
+ private function extract($colour)
+ {
+ $filters = '';
+ $fpos = strpos($colour, '/');
+ if($fpos !== false) {
+ $filters = substr($colour, $fpos + 1);
+ $colour = substr($colour, 0, $fpos);
+ }
+ list($colour, $opacity) = $this->extractOpacity($colour);
+ $this->colour = $this->as_string = $colour;
+ $this->opacity = $opacity;
+
+ // filters don't work on 'none' and 'transparent', so don't try
+ if($filters !== '' && $colour !== 'none' && $colour !== 'transparent') {
+ $cf = new ColourFilter($colour, $filters);
+ $this->colour = $this->as_string = (string)$cf;
+ }
+ }
+
+ /**
+ * Returns an array containing the colour and opacity from a string
+ */
+ public static function extractOpacity($colour)
+ {
+ $opacity = 1.0;
+ if(strpos($colour, ':') !== false) {
+ $parts = explode(':', $colour);
+ if(is_numeric($parts[0]) || count($parts) == 3)
+ $gstop = array_shift($parts);
+
+ $c = array_shift($parts);
+ $ovalue = array_shift($parts);
+ if($ovalue !== null) {
+ if(!is_numeric($ovalue))
+ throw new \Exception('Non-numeric opacity in colour: ' . $colour);
+ $opacity = min(1.0, max(0.0, 1.0 * $ovalue));
+ }
+ $colour = $c;
+ }
+ return [$colour, $opacity];
+ }
+
+ /**
+ * Output for SVG values
+ */
+ public function __toString()
+ {
+ if($this->as_string !== '')
+ return $this->as_string;
+
+ if($this->gradient) {
+ $gradient_id = $this->graph->defs->addGradient($this->colour, $this->key,
+ $this->radial);
+ $this->as_string = 'url(#' . $gradient_id . ')';
+ } elseif($this->pattern) {
+ $pattern_id = $this->graph->defs->addPattern($this->colour);
+ $this->as_string = 'url(#' . $pattern_id . ')';
+ }
+
+ return $this->as_string;
+ }
+
+ /**
+ * Sets the key for use with gradients
+ */
+ public function setGradientKey($key)
+ {
+ $this->key = $key;
+ }
+
+ /**
+ * Returns the opacity value
+ */
+ public function opacity($as_number = false)
+ {
+ if($as_number)
+ return new Number($this->opacity);
+ return $this->opacity;
+ }
+
+ /**
+ * Returns the solid colour
+ */
+ public function solid()
+ {
+ if(!$this->gradient && !$this->pattern)
+ return $this->colour;
+
+ list($solid) = $this->extractOpacity($this->colour[0]);
+ return $solid;
+ }
+
+ /**
+ * Returns the R,G,B for the colour
+ */
+ public function rgb()
+ {
+ $rgb = new RGBColour($this->solid());
+ return $rgb->getRGB();
+ }
+
+ /**
+ * Returns true if the colour is a gradient
+ */
+ public function isGradient()
+ {
+ return $this->gradient;
+ }
+
+ /**
+ * Returns true if the colour is a pattern
+ */
+ public function isPattern()
+ {
+ return $this->pattern;
+ }
+
+ /**
+ * Returns true if the colour is 'none'
+ */
+ public function isNone()
+ {
+ return $this->colour === 'none';
+ }
+
+ /**
+ * Returns true if a radial gradient
+ */
+ public function isRadial()
+ {
+ return $this->gradient && $this->radial;
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/ColourArray.php b/classes/vendor/81x/goat1000/svggraph/ColourArray.php
new file mode 100644
index 0000000..5280f79
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/ColourArray.php
@@ -0,0 +1,73 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class ColourArray implements \ArrayAccess {
+
+ private $colours;
+ private $count;
+
+ public function __construct($colours)
+ {
+ $this->colours = $colours;
+ $this->count = count($colours);
+ }
+
+ /**
+ * Not used by this class
+ */
+ public function setup($count)
+ {
+ // count comes from array, not number of bars etc.
+ }
+
+ /**
+ * always true, because it wraps around
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetExists($offset)
+ {
+ return true;
+ }
+
+ /**
+ * return the colour
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetGet($offset)
+ {
+ return $this->colours[$offset % $this->count];
+ }
+
+ #[\ReturnTypeWillChange]
+ public function offsetSet($offset, $value)
+ {
+ $this->colours[$offset % $this->count] = $value;
+ }
+
+ #[\ReturnTypeWillChange]
+ public function offsetUnset($offset)
+ {
+ throw new \Exception('Unexpected offsetUnset');
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/ColourFilter.php b/classes/vendor/81x/goat1000/svggraph/ColourFilter.php
new file mode 100644
index 0000000..6697483
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/ColourFilter.php
@@ -0,0 +1,122 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * Class for modifying a colour
+ */
+class ColourFilter {
+ private $colour;
+ private $filters;
+
+ public function __construct($colour, $filters)
+ {
+ // these are special cases, can't be processed
+ if($colour === 'transparent' || $colour === 'none')
+ throw new \InvalidArgumentException('Unable to filter colour [' . $colour . ']');
+ $this->colour = new RGBColour($colour);
+
+ $filters = explode('/', $filters);
+ foreach($filters as $f) {
+ $filter = $f;
+ $args = [];
+ $fpos = strpos($f, '(');
+ $epos = strpos($f, ')');
+ if($fpos > 0) {
+ $filter = substr($f, 0, $fpos);
+ $a = '';
+ if($epos > $fpos)
+ $a = substr($f, $fpos + 1, $epos - $fpos - 1);
+ if($a !== '')
+ $args = preg_split('/[\s,]+/', $a);
+ }
+
+ if(method_exists($this, $filter))
+ call_user_func_array([$this, $filter], $args);
+ }
+ }
+
+ /**
+ * Increase or decrease brightness
+ */
+ public function brightness($amount = '1.2')
+ {
+ list ($operator, $value) = $this->expression($amount);
+ if($value === null)
+ throw new \InvalidArgumentException('Invalid brightness [' . $amount . ']');
+ list($h, $s, $l) = $this->colour->getHSL();
+
+ $l = min(1.0, max(0.0, $operator === '+' ? $l + $value : $l * $value));
+ $this->colour->setHSL($h, $s, $l);
+ }
+
+ /**
+ * Increase or decrease saturation
+ */
+ public function saturation($amount = '0.0')
+ {
+ list ($operator, $value) = $this->expression($amount);
+ if($value === null)
+ throw new \InvalidArgumentException('Invalid saturation [' . $amount . ']');
+ list($h, $s, $l) = $this->colour->getHSL();
+
+ $s = min(1.0, max(0.0, $operator === '+' ? $s + $value : $s * $value));
+ $this->colour->setHSL($h, $s, $l);
+ }
+
+ /**
+ * Modify the hue
+ */
+ public function hue($amount = '60')
+ {
+ if(!is_numeric($amount))
+ throw new \InvalidArgumentException('Invalid hue [' . $amount . ']');
+ list($h, $s, $l) = $this->colour->getHSL();
+ $h += $amount;
+ $this->colour->setHSL($h, $s, $l);
+ }
+
+ /**
+ * Returns the expression to be applied
+ */
+ public static function expression($a)
+ {
+ $operator = '*';
+ $value = null;
+ if($a[0] === '+' || $a[0] === '-')
+ $operator = '+';
+
+ $p = strpos($a, '%');
+ if($p > 0)
+ $a = substr($a, 0, $p);
+
+ if(is_numeric($a))
+ $value = $p > 0 ? $a / 100 : $a;
+ return [$operator, $value];
+ }
+
+ public function __toString()
+ {
+ return $this->colour->getHex();
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/ColourGroup.php b/classes/vendor/81x/goat1000/svggraph/ColourGroup.php
new file mode 100644
index 0000000..4ed7b45
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/ColourGroup.php
@@ -0,0 +1,109 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * Class for related stroke and fill colours
+ */
+class ColourGroup {
+ private $stroke;
+
+ public function __construct(&$graph, $item, $key, $dataset,
+ $stroke_opt = 'stroke_colour', $fill = null, $item_opt = null,
+ $stroke_opt_is_colour = false)
+ {
+ $stroke = $stroke_opt_is_colour ? $stroke_opt :
+ $graph->getItemOption($stroke_opt, $dataset, $item, $item_opt);
+ if(is_array($stroke)) {
+ $this->stroke = new Colour($graph, $stroke);
+ return;
+ }
+
+ list ($stroke_colour, $opacity, $filters) = $this->colourParts($stroke);
+
+ // not a fill colour?
+ if($stroke_colour !== 'fill' && $stroke_colour !== 'fillColour') {
+ $this->stroke = new Colour($graph, $stroke);
+ return;
+ }
+
+ $allow_grad_pat = ($stroke_colour === 'fill');
+
+ if($fill !== null) {
+ $stroke_colour = new Colour($graph, $fill, $allow_grad_pat, $allow_grad_pat);
+ } else {
+ $stroke_colour = $graph->getColour($item, $key, $dataset, $allow_grad_pat,
+ $allow_grad_pat);
+ }
+
+ if($stroke_colour->isNone())
+ $stroke_colour = new Colour($graph, 'black');
+
+ $not_solid = $stroke_colour->isGradient() || $stroke_colour->isPattern();
+
+ // if there are no modifications to make, we're done
+ if($not_solid || ($opacity >= 1 && $filters == '')) {
+ $this->stroke = $stroke_colour;
+ return;
+ }
+
+ $stroke = $stroke_colour->solid();
+ if($opacity < 1)
+ $stroke .= ':' . $opacity;
+ if($filters != '')
+ $stroke .= '/' . $filters;
+
+ $this->stroke = new Colour($graph, $stroke);
+ }
+
+ /**
+ * Splits a colour into parts
+ */
+ private static function colourParts($colour)
+ {
+ $opacity = 1;
+ $filters = '';
+
+ if(!empty($colour)) {
+ // get opacity / filters
+ $spos = strpos($colour, '/');
+ if($spos !== false) {
+ $filters = substr($colour, $spos + 1);
+ $colour = substr($colour, 0, $spos);
+ }
+
+ $spos = strpos($colour, ':');
+ if($spos !== false) {
+ $opacity = substr($colour, $spos + 1);
+ $colour = substr($colour, 0, $spos);
+ }
+ }
+
+ return [$colour, $opacity, $filters];
+ }
+
+ public function stroke()
+ {
+ return $this->stroke;
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/ColourRange.php b/classes/vendor/81x/goat1000/svggraph/ColourRange.php
new file mode 100644
index 0000000..1e327a0
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/ColourRange.php
@@ -0,0 +1,68 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * Abstract class implements common methods
+ */
+abstract class ColourRange implements \ArrayAccess {
+
+ protected $count = 2;
+
+ /**
+ * Sets up the length of the range
+ */
+ public function setup($count)
+ {
+ $this->count = $count;
+ }
+
+ /**
+ * always true, because it wraps around
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetExists($offset)
+ {
+ return true;
+ }
+
+ #[\ReturnTypeWillChange]
+ public function offsetSet($offset, $value)
+ {
+ throw new \Exception('Unexpected offsetSet');
+ }
+
+ #[\ReturnTypeWillChange]
+ public function offsetUnset($offset)
+ {
+ throw new \Exception('Unexpected offsetUnset');
+ }
+
+ /**
+ * Clamps a value to range $min-$max
+ */
+ protected static function clamp($val, $min, $max)
+ {
+ return min($max, max($min, $val));
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/ColourRangeHSL.php b/classes/vendor/81x/goat1000/svggraph/ColourRangeHSL.php
new file mode 100644
index 0000000..f4f9b36
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/ColourRangeHSL.php
@@ -0,0 +1,140 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * Colour range for HSL values
+ */
+class ColourRangeHSL extends ColourRange {
+
+ private $h1, $s1, $l1;
+ private $hdiff, $sdiff, $ldiff;
+
+ /**
+ * HSL range
+ */
+ public function __construct($h1, $s1, $l1, $h2, $s2, $l2)
+ {
+ $this->h1 = $this->clamp($h1, 0, 360);
+ $this->s1 = $this->clamp($s1, 0, 1);
+ $this->l1 = $this->clamp($l1, 0, 1);
+
+ $hdiff = $this->clamp($h2, 0, 360) - $this->h1;
+ if(abs($hdiff) > 180)
+ $hdiff += $hdiff < 0 ? 360 : -360;
+ $this->hdiff = $hdiff;
+ $this->sdiff = $this->clamp($s2, 0, 1) - $this->s1;
+ $this->ldiff = $this->clamp($l2, 0, 1) - $this->l1;
+ }
+
+ /**
+ * Reverse direction of colour cycle
+ */
+ public function reverse()
+ {
+ $this->hdiff += $this->hdiff < 0 ? 360 : -360;
+ }
+
+ /**
+ * Return the colour from the range
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetGet($offset)
+ {
+ $c = max($this->count - 1, 1);
+ $offset = $this->clamp($offset, 0, $c);
+ $h = fmod(360 + $this->h1 + $offset * $this->hdiff / $c, 360);
+ $s = $this->s1 + $offset * $this->sdiff / $c;
+ $l = $this->l1 + $offset * $this->ldiff / $c;
+
+ list($r, $g, $b) = $this->hslToRgb($h, $s, $l);
+ return sprintf('#%02x%02x%02x', $r, $g, $b);
+ }
+
+ /**
+ * Factory method creates an instance from RGB values
+ */
+ public static function fromRgb($r1, $g1, $b1, $r2, $g2, $b2)
+ {
+ list($h1, $s1, $l1) = ColourRangeHSL::rgbToHsl($r1, $g1, $b1);
+ list($h2, $s2, $l2) = ColourRangeHSL::rgbToHsl($r2, $g2, $b2);
+ return new ColourRangeHSL($h1, $s1, $l1, $h2, $s2, $l2);
+ }
+
+ /**
+ * Convert RGB to HSL (0-360, 0-1, 0-1)
+ */
+ public static function rgbToHsl($r, $g, $b)
+ {
+ $r1 = ColourRangeHSL::clamp($r, 0, 255) / 255;
+ $g1 = ColourRangeHSL::clamp($g, 0, 255) / 255;
+ $b1 = ColourRangeHSL::clamp($b, 0, 255) / 255;
+ $cmax = max($r1, $g1, $b1);
+ $cmin = min($r1, $g1, $b1);
+ $delta = $cmax - $cmin;
+
+ $l = ($cmax + $cmin) / 2;
+ if($delta == 0) {
+ $h = $s = 0;
+ } else {
+ if($cmax == $r1) {
+ $h = fmod(($g1 - $b1) / $delta, 6);
+ } elseif($cmax == $g1) {
+ $h = 2 + ($b1 - $r1) / $delta;
+ } else {
+ $h = 4 + ($r1 - $g1) / $delta;
+ }
+ $h = fmod(360 + ($h * 60), 360);
+ $s = $delta / (1 - abs(2 * $l - 1));
+ }
+ return [$h, $s, $l];
+ }
+
+ /**
+ * Convert HSL to RGB
+ */
+ public static function hslToRgb($h, $s, $l)
+ {
+ $h1 = ColourRangeHSL::clamp($h, 0, 360);
+ $s1 = ColourRangeHSL::clamp($s, 0, 1);
+ $l1 = ColourRangeHSL::clamp($l, 0, 1);
+
+ $c = (1 - abs(2 * $l1 - 1)) * $s1;
+ $x = $c * (1 - abs(fmod($h1 / 60, 2) - 1));
+ $m = $l1 - $c / 2;
+
+ $c = 255 * ($c + $m);
+ $x = 255 * ($x + $m);
+ $m *= 255;
+ switch(floor($h1 / 60)) {
+ case 0 : $rgb = [$c, $x, $m]; break;
+ case 1 : $rgb = [$x, $c, $m]; break;
+ case 2 : $rgb = [$m, $c, $x]; break;
+ case 3 : $rgb = [$m, $x, $c]; break;
+ case 4 : $rgb = [$x, $m, $c]; break;
+ case 5 : $rgb = [$c, $m, $x]; break;
+ }
+
+ return $rgb;
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/ColourRangeRGB.php b/classes/vendor/81x/goat1000/svggraph/ColourRangeRGB.php
new file mode 100644
index 0000000..99acd89
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/ColourRangeRGB.php
@@ -0,0 +1,59 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * Colour range for RGB values
+ */
+class ColourRangeRGB extends ColourRange {
+
+ private $r1, $g1, $b1;
+ private $rdiff, $gdiff, $bdiff;
+
+ /**
+ * RGB range
+ */
+ public function __construct($r1, $g1, $b1, $r2, $g2, $b2)
+ {
+ $this->r1 = $this->clamp($r1, 0, 255);
+ $this->g1 = $this->clamp($g1, 0, 255);
+ $this->b1 = $this->clamp($b1, 0, 255);
+ $this->rdiff = $this->clamp($r2, 0, 255) - $this->r1;
+ $this->gdiff = $this->clamp($g2, 0, 255) - $this->g1;
+ $this->bdiff = $this->clamp($b2, 0, 255) - $this->b1;
+ }
+
+ /**
+ * Return the colour from the range
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetGet($offset)
+ {
+ $c = max($this->count - 1, 1);
+ $offset = $this->clamp($offset, 0, $c);
+ $r = $this->r1 + $offset * $this->rdiff / $c;
+ $g = $this->g1 + $offset * $this->gdiff / $c;
+ $b = $this->b1 + $offset * $this->bdiff / $c;
+ return sprintf('#%02x%02x%02x', $r, $g, $b);
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/Colours.php b/classes/vendor/81x/goat1000/svggraph/Colours.php
new file mode 100644
index 0000000..76d9406
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/Colours.php
@@ -0,0 +1,201 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class Colours implements \Countable {
+
+ private $colours = [];
+ private $dataset_count = 0;
+ private $fallback = false;
+ private $max_index = 1;
+ private $reverse = false;
+
+ /**
+ * Constructor sets up fallback colour array in case per-dataset
+ * functions are not used
+ */
+ public function __construct($colours = null)
+ {
+ if(is_array($colours))
+ $this->fallback = $colours;
+ else
+ $this->fallback = [
+ '#11c', '#c11', '#cc1', '#1c1', '#c81',
+ '#116', '#611', '#661', '#161', '#631'
+ ];
+ }
+
+ /**
+ * Setup based on graph requirements
+ */
+ public function setup($count, $datasets = null, $reverse = false)
+ {
+ if($this->fallback !== false) {
+ if($datasets !== null) {
+ foreach($this->fallback as $colour) {
+ // in fallback, each dataset gets one colour
+ $this->colours[] = new ColourArray([$colour]);
+ }
+ } else {
+ $this->colours[] = new ColourArray($this->fallback);
+ }
+ $this->dataset_count = count($this->colours);
+ }
+
+ foreach($this->colours as $clist)
+ $clist->setup($count);
+ $this->max_index = $count - 1;
+ $this->reverse = $reverse;
+ }
+
+ /**
+ * Returns the colour for an index and dataset
+ */
+ public function getColour($index, $dataset = null)
+ {
+ // default is for a colour per dataset
+ if($dataset === null)
+ $dataset = 0;
+
+ if($this->reverse)
+ $index = $this->max_index - $index;
+
+ // see if specific dataset exists
+ if(array_key_exists($dataset, $this->colours))
+ return $this->colours[$dataset][$index];
+
+ // try mod
+ if(is_numeric($dataset))
+ $dataset = $dataset % $this->dataset_count;
+ if(array_key_exists($dataset, $this->colours))
+ return $this->colours[$dataset][$index];
+
+ // just use first dataset
+ reset($this->colours);
+ $clist = current($this->colours);
+ return $clist[$index];
+ }
+
+ /**
+ * Implement Countable to make it non-countable
+ */
+ #[\ReturnTypeWillChange]
+ public function count()
+ {
+ throw new \Exception('Cannot count Colours class');
+ return 0;
+ }
+
+ /**
+ * Set an entry in the colours array
+ */
+ private function setDataset($dataset, $colours)
+ {
+ if($this->fallback) {
+ // use fallback for dataset 0 if not already set
+ if($dataset != 0)
+ $this->colours[0] = new ColourArray($this->fallback);
+ $this->fallback = false;
+ }
+
+ $this->colours[$dataset] = $colours;
+ $this->dataset_count = count($this->colours);
+ }
+
+ /**
+ * Assign a colour array for a dataset
+ */
+ public function set($dataset, $colours)
+ {
+ if($colours === null) {
+ if(array_key_exists($dataset, $this->colours))
+ unset($this->colours[$dataset]);
+ return;
+ }
+ $this->setDataset($dataset, new ColourArray($colours));
+ }
+
+ /**
+ * Set up RGB colour range
+ */
+ public function rangeRGB($dataset, $r1, $g1, $b1, $r2, $g2, $b2)
+ {
+ $this->setDataset($dataset,
+ new ColourRangeRGB($r1, $g1, $b1, $r2, $g2, $b2));
+ }
+
+ /**
+ * HSL colour range, with option to go the long way
+ */
+ public function rangeHSL($dataset, $h1, $s1, $l1, $h2, $s2, $l2,
+ $reverse = false)
+ {
+ $rng = new ColourRangeHSL($h1, $s1, $l1, $h2, $s2, $l2);
+ if($reverse)
+ $rng->reverse();
+ $this->setDataset($dataset, $rng);
+ }
+
+ /**
+ * HSL colour range from RGB values, with option to go the long way
+ */
+ public function rangeRGBtoHSL($dataset, $r1, $g1, $b1, $r2, $g2, $b2,
+ $reverse = false)
+ {
+ $rng = ColourRangeHSL::fromRGB($r1, $g1, $b1, $r2, $g2, $b2);
+ if($reverse)
+ $rng->reverse();
+ $this->setDataset($dataset, $rng);
+ }
+
+ /**
+ * RGB colour range from two RGB hex codes
+ */
+ public function rangeHexRGB($dataset, $c1, $c2)
+ {
+ list($r1, $g1, $b1) = $this->hexRGB($c1);
+ list($r2, $g2, $b2) = $this->hexRGB($c2);
+ $this->rangeRGB($dataset, $r1, $g1, $b1, $r2, $g2, $b2);
+ }
+
+ /**
+ * HSL colour range from RGB hex codes
+ */
+ public function rangeHexHSL($dataset, $c1, $c2, $reverse = false)
+ {
+ list($r1, $g1, $b1) = $this->hexRGB($c1);
+ list($r2, $g2, $b2) = $this->hexRGB($c2);
+ $this->rangeRGBtoHSL($dataset, $r1, $g1, $b1, $r2, $g2, $b2, $reverse);
+ }
+
+ /**
+ * Convert a colour code to RGB array
+ */
+ public static function hexRGB($c)
+ {
+ // support filters, other colour formats by using Colour class
+ $graph = null;
+ $cc = new Colour($graph, $c, false, false, false);
+ return $cc->rgb();
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/ContextMenu.php b/classes/vendor/81x/goat1000/svggraph/ContextMenu.php
new file mode 100644
index 0000000..9d08d4c
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/ContextMenu.php
@@ -0,0 +1,170 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * Right-click (or long touch) context menu
+ */
+class ContextMenu {
+
+ private $graph;
+ private $js;
+
+ private $function_added = false;
+ private $callback;
+ private $use_structure = false;
+ private $namespace = '';
+
+ /**
+ * Constructor sets up options and root menu
+ */
+ public function __construct(&$graph, &$javascript)
+ {
+ if(!$graph->getOption('show_context_menu'))
+ return;
+ $this->graph =& $graph;
+ $this->js =& $javascript;
+
+ $this->callback = $graph->getOption('context_callback');
+ $structure = $graph->getOption('structure');
+ if(is_array($structure) && isset($structure['context_menu']))
+ $this->use_structure = true;
+ if($graph->getOption('namespace'))
+ $this->namespace = 'svg:';
+
+ $global = $graph->getOption('context_global');
+ if($global !== false) {
+ if($global === null)
+ $global = [ [SVGGraph::VERSION, null] ];
+
+ $entries = '';
+ foreach($global as $entry) {
+ $attr = ['name' => $entry[0], 'link' => $entry[1]];
+ $entries .= $graph->element('svggraph:menuitem', $attr);
+ }
+ $menu = $graph->element('svggraph:menu', null, null, $entries);
+ $xml = $graph->element('svggraph:data',
+ ['xmlns:svggraph' => 'http://www.goat1000.com/svggraph'], null, $menu);
+ $graph->defs->add($xml);
+ }
+ }
+
+ /**
+ * Adds the javascript function
+ */
+ public function addFunction()
+ {
+ $this->js->addFuncs('getE', 'finditem', 'newel', 'newtext',
+ 'svgNode', 'setattr', 'getData', 'svgCursorCoords');
+ $this->js->addInitFunction('contextMenuInit');
+
+ $opts = ['link_target', 'link_underline', 'stroke_width', 'round', 'font',
+ 'font_weight', 'document_menu', 'spacing', 'min_width',
+ 'shadow_opacity', 'mouseleave'];
+ $colours = ['colour', 'link_colour', 'link_hover_colour', 'back_colour'];
+ $vars = [];
+ foreach($opts as $opt)
+ $vars[$opt] = $this->graph->getOption('context_' . $opt);
+ $vars['font_size'] = Number::units($this->graph->getOption('context_font_size'));
+ foreach($colours as $opt)
+ $vars[$opt] = new Colour($this->graph, $this->graph->getOption('context_' . $opt));
+
+ $svg_text = new Text($this->graph, $vars['font']);
+ list(, $text_height) = $svg_text->measure('Test', $vars['font_size']);
+ $text_baseline = $svg_text->baseline($vars['font_size']);
+
+ $vars['pad_x'] = $this->graph->getOption('context_padding_x', 'context_padding');
+ $vars['pad_y'] = $this->graph->getOption('context_padding_y', 'context_padding');
+ $vars['text_start'] = $vars['pad_y'] + $text_baseline;
+ $vars['rect_start'] = $vars['pad_y'] - $vars['spacing'] / 2;
+ $vars['spacing'] += $text_height;
+
+ $vars['round_part'] = $vars['mouseleave'] = $vars['underline_part'] = '';
+ if($vars['link_underline'])
+ $vars['underline_part'] = ", 'text-decoration': 'underline'";
+ if($vars['round']) {
+ $rnum = new Number($vars['round']);
+ $vars['round_part'] = ', rx:"' . $rnum . 'px", ry:"' . $rnum . 'px"';
+ }
+ $cmoffs = 0;
+ $half_stroke = $vars['stroke_width'] / 2;
+ $vars['pad_x'] += $half_stroke;
+ $vars['pad_y'] += $half_stroke;
+
+ $vars['off_right'] = $vars['stroke_width'];
+ $vars['off_bottom'] = $vars['stroke_width'];
+ if(is_numeric($vars['shadow_opacity'])) {
+ $cmoffs = 4;
+ $vars['off_right'] += $cmoffs;
+ $vars['off_bottom'] += $cmoffs;
+ }
+ $vars['cmoffs'] = $cmoffs;
+
+ if($vars['document_menu']) {
+ $this->js->insertFunction('rootContextMenu',
+ "function rootContextMenu(){closeContextMenu();}\n");
+ } else {
+ $this->js->insertTemplate('rootContextMenu');
+ }
+
+ if((int)$vars['mouseleave'] > 0) {
+ $mlnum = new Number($mouseleave);
+ $vars['mouseleave'] = 'e[c].addEventListener("mouseleave",function(e) {' .
+ 'setTimeout(closeContextMenu,' . $mlnum . ');}, false);';
+ }
+
+ $vars['namespace'] = $this->namespace;
+ $this->js->insertTemplate('contextMenu', $vars);
+ $this->function_added = true;
+ }
+
+ /**
+ * Adds context menu for item
+ */
+ public function setMenu(&$element, $dataset, &$item, $duplicate = false)
+ {
+ $menu = null;
+ if(is_callable($this->callback)) {
+ $menu = call_user_func($this->callback, $dataset, $item->key, $item->value);
+ } elseif($this->use_structure) {
+ $menu = $item->context_menu;
+ }
+
+ if(is_array($menu)) {
+ if(!isset($element['id']))
+ $element['id'] = $this->graph->newID();
+ $var = json_encode($menu);
+ $this->js->insertVariable('menus', $element['id'], $var, false);
+ if($duplicate)
+ $this->js->addOverlay($element['id'], $this->graph->newID());
+ } else {
+ // add a placeholder to make sure the variable exists
+ $ignore_id = $this->graph->newID();
+ $this->js->insertVariable('menus', $ignore_id, "''", false);
+ }
+
+ // set up menus after duplication
+ if(!$this->function_added)
+ $this->addFunction();
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/Coords.php b/classes/vendor/81x/goat1000/svggraph/Coords.php
new file mode 100644
index 0000000..3d21d84
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/Coords.php
@@ -0,0 +1,212 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * Class for parsing and converting coordinates
+ */
+class Coords {
+
+ private $graph;
+
+ public function __construct(&$graph)
+ {
+ $this->graph =& $graph;
+ }
+
+ /**
+ * Returns TRUE if (x,y) is grid-based
+ */
+ public function isGrid($x, $y)
+ {
+ if(is_numeric($x) && is_numeric($y))
+ return false;
+ $first = strtolower(substr($x, 0, 1));
+ if($first == 'g')
+ return true;
+ $first = strtolower(substr($y, 0, 1));
+ if($first == 'g')
+ return true;
+ return false;
+ }
+
+ /**
+ * splits $value, removing leading char and updating $axis, $axis_no
+ */
+ private static function valueAxis(&$value, &$axis, &$axis_no)
+ {
+ if(preg_match('/^[ug](.*?)(([xy])(\d?))?$/i', $value, $matches)) {
+ $value = $matches[1];
+ if(count($matches) == 5) {
+ $axis = strtolower($matches[3]);
+ $axis_no = is_numeric($matches[4]) ? $matches[4] : null;
+ }
+ return;
+ }
+ // if the regex failed (?!) just strip leading u or g
+ $value = substr($value, 1);
+ }
+
+ /**
+ * Transform coordinate pair to SVG coords
+ */
+ public function transformCoords($x, $y)
+ {
+ $xy = [ $this->transform($x, 'x'), $this->transform($y, 'y') ];
+ if($this->isGrid($x, $y) && method_exists($this->graph, 'transformCoords')) {
+ $xy = $this->graph->transformCoords($xy[0], $xy[1]);
+ }
+ return $xy;
+ }
+
+ /**
+ * Determines the type of value
+ */
+ public static function parseValue($value, $axis = null)
+ {
+ $info = [
+ 'value' => $value, 'axis' => $axis, 'axis_no' => null,
+ 'simple' => true, 'grid' => false, 'units' => false,
+ 'offset' => 0, 'offset_units' => false,
+ ];
+ if(is_numeric($value) || !is_string($value))
+ return $info;
+
+ $info['simple'] = false;
+ $first = strtolower(substr($value, 0, 1));
+ if($first == 'u' || $first == 'g') {
+ Coords::valueAxis($value, $axis, $axis_no);
+ $info['value'] = $value;
+ $info['axis'] = $axis;
+ $info['axis_no'] = $axis_no;
+ $info['grid'] = true;
+ $info['units'] = ($first == 'u');
+ $first = strtolower(substr($value, 0, 1));
+ }
+
+ // check for offset from relative position
+ if(!$info['units'] && in_array($first, ['t','l','b','r','h','w','c']) &&
+ preg_match('/(.+)([-+][0-9.]+)(u?)/', $info['value'], $matches)) {
+ $info['value'] = $matches[1];
+ $info['offset'] = $matches[2];
+ $info['offset_units'] = ($matches[3] == 'u' || $matches[3] == 'U');
+ }
+ return $info;
+ }
+
+ /**
+ * Transform from grid space etc. to SVG space
+ */
+ public function transform($value, $axis, $default_pos = 0, $measure_from = 0)
+ {
+ $v_info = Coords::parseValue($value, $axis);
+ if($v_info['simple'])
+ return $value;
+
+ $value = $v_info['value'];
+ if($v_info['grid'] && !method_exists($this->graph, 'gridX'))
+ throw new \Exception('Invalid dimensions (non-grid graph)');
+
+ if($v_info['units']) {
+ $axis_inst = $this->graph->getAxis($v_info['axis'], $v_info['axis_no']);
+ return $axis_inst->measureUnits($measure_from, $value);
+ }
+
+ $dim = $this->graph->getDimensions();
+
+ // try value as assoc/datetime key first
+ if($v_info['grid']) {
+ $axis_inst = $this->graph->getAxis($v_info['axis'], $v_info['axis_no']);
+ $position = $axis_inst->positionByKey($value);
+ if($position !== null) {
+ if($v_info['axis'] == 'x')
+ return $position + $dim['pad_left'];
+ return $axis_inst->reversed() ?
+ $dim['height'] - $dim['pad_bottom'] - $position :
+ $position + $dim['pad_top'];
+ }
+ }
+
+ if(is_numeric($value)) {
+ if($v_info['grid']) {
+ $func = $axis == 'x' ? 'gridX' : 'gridY';
+ return $this->graph->{$func}($value, $v_info['axis_no']);
+ }
+ return $value;
+ }
+
+ if($value == 'c')
+ $value .= $axis;
+ $pos = $v_info['grid'] ? $this->getGridPosition($value, $default_pos) :
+ $this->getGraphPosition($value, $default_pos);
+
+ // handle offset from relative position
+ if($v_info['offset']) {
+ if($v_info['offset_units']) {
+ $axis_inst = $this->graph->getAxis($v_info['axis'], $v_info['axis_no']);
+ $pos += $axis_inst->measureUnits($measure_from, $v_info['offset']);
+ } else {
+ $pos += $v_info['offset'];
+ }
+ }
+ return $pos;
+ }
+
+ /**
+ * Converts a grid position to a number
+ */
+ public function getGridPosition($pos, $default_pos)
+ {
+ $dim = $this->graph->getDimensions();
+ switch($pos) {
+ case 't' : return $dim['pad_top'];
+ case 'l' : return $dim['pad_left'];
+ case 'b' : return $dim['height'] - $dim['pad_bottom'];
+ case 'r' : return $dim['width'] - $dim['pad_right'];
+ case 'h' : return $dim['height'] - $dim['pad_bottom'] - $dim['pad_top'];
+ case 'w' : return $dim['width'] - $dim['pad_right'] - $dim['pad_left'];
+ case 'cx' : return ($dim['width'] - $dim['pad_right'] + $dim['pad_left']) / 2;
+ case 'cy' : return ($dim['height'] - $dim['pad_bottom'] + $dim['pad_top']) / 2;
+ }
+ return $default_pos;
+ }
+
+ /**
+ * Converts a graph position to a number
+ */
+ public function getGraphPosition($pos, $default_pos)
+ {
+ $dim = $this->graph->getDimensions();
+ switch($pos) {
+ case 't' : return 0;
+ case 'l' : return 0;
+ case 'b' : return $dim['height'];
+ case 'r' : return $dim['width'];
+ case 'h' : return $dim['height'];
+ case 'w' : return $dim['width'];
+ case 'cx' : return $dim['width'] / 2;
+ case 'cy' : return $dim['height'] / 2;
+ }
+ return $default_pos;
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/CrossHairs.php b/classes/vendor/81x/goat1000/svggraph/CrossHairs.php
new file mode 100644
index 0000000..8caa637
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/CrossHairs.php
@@ -0,0 +1,303 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class CrossHairs {
+
+ private $graph;
+ private $show_h = false;
+ private $show_v = false;
+
+ private $left, $top, $width, $height;
+ private $x_axis, $y_axis;
+ private $assoc;
+ private $flip_axes;
+ private $encoding;
+
+ public function __construct(&$graph, $left, $top, $w, $h, $x_axis, $y_axis,
+ $assoc, $flip_axes, $encoding)
+ {
+ if($graph->getOption('crosshairs')) {
+ $this->show_h = $graph->getOption('crosshairs_show_h');
+ $this->show_v = $graph->getOption('crosshairs_show_v');
+ }
+
+ if(!$this->show_h && !$this->show_v)
+ return;
+
+ $this->left = $left;
+ $this->top = $top;
+ $this->width = $w;
+ $this->height = $h;
+ $this->x_axis = $x_axis;
+ $this->y_axis = $y_axis;
+ $this->assoc = $assoc;
+ $this->flip_axes = $flip_axes;
+ $this->encoding = $encoding;
+ $this->graph =& $graph;
+ }
+
+ /**
+ * Returns TRUE if the crosshairs are enabled
+ */
+ public function enabled()
+ {
+ return $this->show_h || $this->show_v;
+ }
+
+ /**
+ * Returns a horizontal or vertical hair
+ */
+ private function getHair($orientation, $ch)
+ {
+ // line is always added, stays hidden if not enabled
+ if($orientation == 'h')
+ $hch = ['class' => 'chX', 'x2' => $ch['x1'] + $this->width];
+ else
+ $hch = ['class' => 'chY', 'y2' => $ch['y1'] + $this->height];
+
+ $show = 'show_' . $orientation;
+ if($this->$show) {
+ $stroke = $this->graph->getOption('crosshairs_colour_' . $orientation,
+ 'crosshairs_colour');
+ $hch['stroke'] = new Colour($this->graph, $stroke, false, false);
+ $hch['stroke-width'] = $this->graph->getOption(
+ 'crosshairs_stroke_width_' . $orientation, 'crosshairs_stroke_width');
+ $opacity = $this->graph->getOption(
+ 'crosshairs_opacity_' . $orientation, 'crosshairs_opacity');
+ if($opacity > 0 && $opacity < 1)
+ $hch['opacity'] = $opacity;
+ $dash = $this->graph->getOption('crosshairs_dash_' . $orientation,
+ 'crosshairs_dash');
+ if(!empty($dash))
+ $hch['stroke-dasharray'] = $dash;
+ }
+ return $this->graph->element('line', array_merge($ch, $hch));
+ }
+
+ /**
+ * Returns the crosshair code and also adds the JS and defs
+ */
+ public function getCrossHairs()
+ {
+ if(!($this->show_v || $this->show_h))
+ return '';
+
+ // make the crosshair lines
+ $crosshairs = '';
+ $ch = [
+ 'x1' => $this->left, 'y1' => $this->top,
+ 'x2' => $this->left, 'y2' => $this->top,
+ 'visibility' => 'hidden', // don't show them to start with!
+ ];
+
+ $crosshairs .= $this->getHair('h', $ch);
+ $crosshairs .= $this->getHair('v', $ch);
+
+ $text_options = [
+ 'back_colour', 'round', 'stroke_width', 'colour', 'font_size', 'font',
+ 'font_weight', 'padding', 'space',
+ ];
+ $t_opt = [];
+ foreach($text_options as $opt)
+ $t_opt[$opt] = $this->graph->getOption('crosshairs_text_' . $opt);
+
+
+ // text group for grid details
+ $text_group = ['id' => $this->graph->newId(), 'visibility' => 'hidden'];
+ $text_rect = [
+ 'x' => '0', 'y' => '0', 'width' => '10', 'height' => '10',
+ 'fill' => new Colour($this->graph, $t_opt['back_colour']),
+ ];
+ if($t_opt['round'])
+ $text_rect['rx'] = $text_rect['ry'] = $t_opt['round'];
+ if($t_opt['stroke_width']) {
+ $text_rect['stroke-width'] = $t_opt['stroke_width'];
+ $text_rect['stroke'] = $t_opt['colour'];
+ }
+ $font_size = max(3, Number::units($t_opt['font_size']));
+ $text_element = [
+ 'x' => 0, 'y' => $font_size,
+ 'font-family' => $t_opt['font'],
+ 'font-size' => $font_size,
+ 'fill' => new Colour($this->graph, $t_opt['colour']),
+ ];
+ $weight = $t_opt['font_weight'];
+ if($weight && $weight != 'normal')
+ $text_element['font-weight'] = $weight;
+
+ $svg_text = new Text($this->graph);
+ $text = $this->graph->element('g', $text_group, null,
+ $this->graph->element('rect', $text_rect) .
+ $svg_text->text('', $font_size, $text_element));
+ $this->graph->addBackMatter($text);
+
+ // add in the details of the grid scales
+ $zero_x = $this->x_axis->zero();
+ $scale_x = $this->x_axis->unit();
+ $zero_y = $this->y_axis->zero();
+ $scale_y = $this->y_axis->unit();
+ $prec_x = $this->graph->getOption('crosshairs_text_precision_h',
+ max(0, ceil(log10($scale_x))));
+ $prec_y = $this->graph->getOption('crosshairs_text_precision_v',
+ max(0, ceil(log10($scale_y))));
+
+ $scale_x = new Number($scale_x);
+ $scale_x->precision = 7;
+ $scale_y = new Number($scale_y);
+ $scale_y->precision = 7;
+
+ $gridx_attrs = [
+ 'function' => 'strValueX',
+ 'zero' => $zero_x,
+ 'scale' => $scale_x,
+ 'precision' => $prec_x,
+ ];
+ $gridy_attrs = [
+ 'function' => 'strValueY',
+ 'zero' => $zero_y,
+ 'scale' => $scale_y,
+ 'precision' => $prec_y,
+ ];
+ $chtextitem_attrs = [
+ 'type' => 'xy',
+ 'groupid' => $text_group['id'],
+ ];
+ $u = $this->x_axis->afterUnits();
+ if(!empty($u))
+ $chtextitem_attrs['unitsx'] = $u;
+ $u = $this->y_axis->afterUnits();
+ if(!empty($u))
+ $chtextitem_attrs['unitsy'] = $u;
+ $u = $this->x_axis->beforeUnits();
+ if(!empty($u))
+ $chtextitem_attrs['unitsbx'] = $u;
+ $u = $this->y_axis->beforeUnits();
+ if(!empty($u))
+ $chtextitem_attrs['unitsby'] = $u;
+
+ $log_x = $this->graph->getOption(['log_axis_x', 0]);
+ $log_y = $this->graph->getOption(['log_axis_y', 0]);
+ if($log_x || $log_y) {
+ $base_y = $this->graph->getOption('log_axis_y_base');
+ $base_x = $this->graph->getOption('log_axis_x_base');
+ $log_h = $this->flip_axes ? $log_y : $log_x;
+ $log_v = $this->flip_axes ? $log_x : $log_y;
+
+ if($log_h) {
+ $gridx_attrs['base'] = $this->flip_axes ? $base_y : $base_x;
+ $gridx_attrs['zero'] = $this->x_axis->value(0);
+ $gridx_attrs['scale'] = $this->x_axis->value($this->width);
+ $this->graph->getJavascript()->addFunction('logStrValueX');
+ $gridx_attrs['function'] = 'logStrValueX';
+ }
+ if($log_v) {
+ $gridy_attrs['base'] = $this->flip_axes ? $base_x : $base_y;
+ $gridy_attrs['zero'] = $this->y_axis->value(0);
+ $gridy_attrs['scale'] = $this->y_axis->value($this->height);
+ $this->graph->getJavascript()->addFunction('logStrValueY');
+ $gridy_attrs['function'] = 'logStrValueY';
+ }
+ }
+
+ if($this->graph->getOption('datetime_keys') &&
+ (method_exists($this->x_axis, 'GetFormat') ||
+ method_exists($this->y_axis, 'GetFormat'))) {
+ $dtf = new DateTimeFormatter;
+ if($this->flip_axes) {
+ $this->graph->getJavascript()->addFunction('dateStrValueY');
+ $zy = (int)$this->y_axis->value(0);
+ $ey = (int)$this->y_axis->value($this->width);
+ $dt = new \DateTime('@' . $zy);
+ $gridy_attrs['scale'] = ($ey - $zy) / $this->height;
+ $gridy_attrs['zero'] = $dtf->format($dt, 'c', true);
+ $gridy_attrs['function'] = 'dateStrValueY';
+ $gridy_attrs['format'] = $this->y_axis->getFormat();
+ } else {
+ $this->graph->getJavascript()->addFunction('dateStrValueX');
+ $zx = (int)$this->x_axis->value(0);
+ $ex = (int)$this->x_axis->value($this->width);
+ $dt = new \DateTime('@' . $zx);
+ $gridx_attrs['scale'] = ($ex - $zx) / $this->width;
+ $gridx_attrs['zero'] = $dtf->format($dt, 'c', true);
+ $gridx_attrs['function'] = 'dateStrValueX';
+ $gridx_attrs['format'] = $this->x_axis->getFormat();
+ }
+ $long_days = $dtf->getLongDays();
+ $short_days = $dtf->getShortDays();
+ $long_months = $dtf->getLongMonths();
+ $short_months = $dtf->getShortMonths();
+ foreach($long_days as $day)
+ $this->graph->getJavascript()->insertVariable('daysLong', null, $day);
+ foreach($short_days as $day)
+ $this->graph->getJavascript()->insertVariable('daysShort', null, $day);
+ foreach($long_months as $month)
+ $this->graph->getJavascript()->insertVariable('monthsLong', null, $month);
+ foreach($short_months as $month)
+ $this->graph->getJavascript()->insertVariable('monthsShort', null, $month);
+ }
+
+ // build associative data keys XML
+ $keys_xml = '';
+ if($this->assoc) {
+
+ $k_max = $this->graph->getMaxKey();
+ for($i = 0; $i <= $k_max; ++$i) {
+ $k = $this->graph->getKey($i);
+ $keys_xml .= $this->graph->element('svggraph:key', ['value' => $k]);
+ }
+ $keys_xml = $this->graph->element('svggraph:keys', null, null, $keys_xml);
+
+ // choose a rounding function
+ $round_function = 'kround';
+ if($this->graph->getOption('label_centre'))
+ $round_function = 'kroundDown';
+ $this->graph->getJavascript()->addFunction($round_function);
+
+ // set the string function
+ if($this->flip_axes) {
+ $this->graph->getJavascript()->addFunction('keyStrValueY');
+ $gridy_attrs['function'] = 'keyStrValueY';
+ $gridy_attrs['round'] = $round_function;
+ } else {
+ $this->graph->getJavascript()->addFunction('keyStrValueX');
+ $gridx_attrs['function'] = 'keyStrValueX';
+ $gridx_attrs['round'] = $round_function;
+ }
+ }
+
+ $gridx = $this->graph->element('svggraph:gridx', $gridx_attrs);
+ $gridy = $this->graph->element('svggraph:gridy', $gridy_attrs);
+ $chtext = $this->graph->element('svggraph:chtext', null, null,
+ $this->graph->element('svggraph:chtextitem', $chtextitem_attrs));
+
+ $xml = $gridx . $gridy . $chtext . $keys_xml;
+ $defs = $this->graph->element('svggraph:data',
+ ['xmlns:svggraph' => 'http://www.goat1000.com/svggraph'], null, $xml);
+ $this->graph->defs->add($defs);
+
+ // add the main function at the end - it can fill in any defaults
+ $this->graph->getJavascript()->addFunction('crosshairs');
+ return $crosshairs;
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/CylinderGraph.php b/classes/vendor/81x/goat1000/svggraph/CylinderGraph.php
new file mode 100644
index 0000000..cae1bb6
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/CylinderGraph.php
@@ -0,0 +1,44 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class CylinderGraph extends Bar3DGraph {
+
+ public function __construct($w, $h, array $settings, array $fixed_settings = [])
+ {
+ $this->bar_class = 'Goat1000\\SVGGraph\\Bar3DCylinder';
+ parent::__construct($w, $h, $settings, $fixed_settings);
+ }
+
+ /**
+ * Set the bar width and space
+ */
+ protected function setBarWidth($width, $space)
+ {
+ parent::setBarWidth($width, $space);
+
+ // translation for cylinders added to 3D bar offset
+ list($sx, $sy) = $this->project(0, 0, $width);
+ $this->tx += ($width + $sx) / 2;
+ $this->ty += $sy / 2;
+ }
+}
diff --git a/classes/vendor/81x/goat1000/svggraph/Data.php b/classes/vendor/81x/goat1000/svggraph/Data.php
new file mode 100644
index 0000000..974431f
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/Data.php
@@ -0,0 +1,293 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * Class for standard data
+ */
+class Data implements \Countable, \ArrayAccess, \Iterator {
+
+ private $datasets = 0;
+ private $data;
+ private $assoc = null;
+ private $datetime = null;
+ private $min_value = [];
+ private $max_value = [];
+ private $min_key = [];
+ private $max_key = [];
+ public $error = null;
+
+ public function __construct(&$data, $force_assoc, $datetime_keys)
+ {
+ if(empty($data[0])) {
+ $this->error = 'No data';
+ return;
+ }
+ $this->data = $data;
+ $this->datasets = count($data);
+ if($force_assoc)
+ $this->assoc = true;
+ if($datetime_keys) {
+ if($this->rekey('Goat1000\\SVGGraph\\Graph::dateConvert')) {
+ $this->datetime = true;
+ $this->assoc = false;
+ return;
+ }
+ $this->error = 'Too many date/time conversion errors';
+ }
+ }
+
+ /**
+ * Implement Iterator interface to prevent iteration...
+ */
+ private function notIterator()
+ {
+ throw new \Exception('Cannot iterate ' . __CLASS__);
+ }
+ #[\ReturnTypeWillChange]
+ public function current() { $this->notIterator(); }
+ #[\ReturnTypeWillChange]
+ public function key() { $this->notIterator(); }
+ #[\ReturnTypeWillChange]
+ public function next() { $this->notIterator(); }
+ #[\ReturnTypeWillChange]
+ public function rewind() { $this->notIterator(); }
+ #[\ReturnTypeWillChange]
+ public function valid() { $this->notIterator(); }
+
+ /**
+ * ArrayAccess methods
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetExists($offset)
+ {
+ return array_key_exists($offset, $this->data);
+ }
+
+ #[\ReturnTypeWillChange]
+ public function offsetGet($offset)
+ {
+ return new DataIterator($this->data, $offset);
+ }
+
+ /**
+ * Don't allow writing to the data
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetSet($offset, $value)
+ {
+ throw new \Exception('Read-only');
+ }
+ #[\ReturnTypeWillChange]
+ public function offsetUnset($offset)
+ {
+ throw new \Exception('Read-only');
+ }
+
+ /**
+ * Countable method
+ */
+ #[\ReturnTypeWillChange]
+ public function count()
+ {
+ return $this->datasets;
+ }
+
+ /**
+ * Returns minimum data value for a dataset
+ */
+ public function getMinValue($dataset = 0)
+ {
+ if(!isset($this->min_value[$dataset])) {
+ $this->min_value[$dataset] = null;
+ if(count($this->data[$dataset]))
+ $this->min_value[$dataset] = Graph::min($this->data[$dataset]);
+ }
+ return $this->min_value[$dataset];
+ }
+
+ /**
+ * Returns maximum data value for a dataset
+ */
+ public function getMaxValue($dataset = 0)
+ {
+ if(!isset($this->max_value[$dataset])) {
+ $this->max_value[$dataset] = null;
+ if(count($this->data[$dataset]))
+ $this->max_value[$dataset] = max($this->data[$dataset]);
+ }
+ return $this->max_value[$dataset];
+ }
+
+ /**
+ * Returns the minimum key value
+ */
+ public function getMinKey($dataset = 0)
+ {
+ if(!isset($this->min_key[$dataset])) {
+ $this->min_key[$dataset] = null;
+ if(count($this->data[$dataset])) {
+ $this->min_key[$dataset] = $this->associativeKeys() ? 0 :
+ min(array_keys($this->data[$dataset]));
+ }
+ }
+ return $this->min_key[$dataset];
+ }
+
+ /**
+ * Returns the maximum key value
+ */
+ public function getMaxKey($dataset = 0)
+ {
+ if(!isset($this->max_key[$dataset])) {
+ $this->max_key[$dataset] = null;
+ if(count($this->data[$dataset])) {
+ $this->max_key[$dataset] = $this->associativeKeys() ?
+ count($this->data[$dataset]) - 1 :
+ max(array_keys($this->data[$dataset]));
+ }
+ }
+ return $this->max_key[$dataset];
+ }
+
+ /**
+ * Returns the key at a given index
+ */
+ public function getKey($index, $dataset = 0)
+ {
+ if(!$this->associativeKeys())
+ return $index;
+
+ // round index to nearest integer, or PHP will floor() it
+ $index = (int)round($index);
+ if($index >= 0) {
+ $slice = array_slice($this->data[$dataset], $index, 1, true);
+ // use foreach to get key and value
+ foreach($slice as $k => $v)
+ return $k;
+ }
+ return null;
+ }
+
+ /**
+ * Returns TRUE if the keys are associative
+ */
+ public function associativeKeys()
+ {
+ if($this->assoc !== null)
+ return $this->assoc;
+
+ foreach(array_keys($this->data[0]) as $k)
+ if(!is_integer($k))
+ return ($this->assoc = true);
+ return ($this->assoc = false);
+ }
+
+ /**
+ * Returns the number of data items
+ */
+ public function itemsCount($dataset = 0)
+ {
+ if($dataset < 0)
+ $dataset = 0;
+ return count($this->data[$dataset]);
+ }
+
+ /**
+ * Returns the min and max sum values
+ */
+ public function getMinMaxSumValues($start = 0, $end = null)
+ {
+ if($start != 0 || ($end !== null && $end != 0))
+ throw new \Exception('Dataset not found');
+
+ // structured data is used for multi-data, so just
+ // return the min and max
+ return [$this->getMinValue(), $this->getMaxValue()];
+ }
+
+ /**
+ * Returns the min/max sum values for an array of datasets
+ */
+ public function getMinMaxSumValuesFor($datasets)
+ {
+ // Data class can't handle multiple datasets
+ if(count($datasets) > 1)
+ throw new \InvalidArgumentException('Multiple datasets not supported');
+
+ $d = array_pop($datasets);
+ if($d < 0 || $d >= $this->datasets)
+ throw new \Exception('Dataset not found');
+
+ return [$this->getMinValue($d), $this->getMaxValue($d)];
+ }
+
+ /**
+ * Returns TRUE if the item exists, setting the $value
+ */
+ public function getData($index, $name, &$value)
+ {
+ // base class doesn't support this, so always return false
+ return false;
+ }
+
+ /**
+ * Doesn't return a structured data item
+ */
+ public function getItem($index, $dataset = 0)
+ {
+ return null;
+ }
+
+ /**
+ * Transforms the keys using a callback function
+ */
+ public function rekey($callback)
+ {
+ $new_data = [];
+ $count = $invalid = 0;
+ for($d = 0; $d < $this->datasets; ++$d) {
+ $new_data[$d] = [];
+ foreach($this->data[$d] as $key => $value) {
+ $new_key = call_user_func($callback, $key);
+
+ // if the callback returns null, skip the value
+ if($new_key === null) {
+ ++$invalid;
+ continue;
+ }
+
+ $new_data[$d][$new_key] = $value;
+ }
+ ++$count;
+ }
+ // if too many invalid, probably a format error
+ if($count && $invalid / $count > 0.05)
+ return false;
+ $this->data = $new_data;
+ // forget previous min/max
+ $this->min_key = [];
+ $this->max_key = [];
+ return true;
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/DataItem.php b/classes/vendor/81x/goat1000/svggraph/DataItem.php
new file mode 100644
index 0000000..f343780
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/DataItem.php
@@ -0,0 +1,54 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * Class for single data items
+ */
+class DataItem {
+
+ public $key;
+ public $value;
+
+ public function __construct($key, $value)
+ {
+ $this->key = $key;
+ $this->value = $value;
+ }
+
+ /**
+ * A getter for extra fields - there are none, so return NULL
+ */
+ public function __get($field)
+ {
+ return null;
+ }
+
+ /**
+ * Returns NULL because standard data doesn't support extra fields
+ */
+ public function data($field)
+ {
+ return null;
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/DataIterator.php b/classes/vendor/81x/goat1000/svggraph/DataIterator.php
new file mode 100644
index 0000000..890f01c
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/DataIterator.php
@@ -0,0 +1,98 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * Class to iterate over standard data
+ */
+class DataIterator implements \Iterator {
+
+ private $data = 0;
+ private $dataset = 0;
+ private $position = 0;
+ private $count = 0;
+
+ public function __construct(&$data, $dataset)
+ {
+ $this->dataset = $dataset;
+ $this->data =& $data;
+ $this->count = count($data[$dataset]);
+ }
+
+ /**
+ * Iterator methods
+ */
+ #[\ReturnTypeWillChange]
+ public function current()
+ {
+ return $this->getItemByIndex($this->position);
+ }
+
+ #[\ReturnTypeWillChange]
+ public function key()
+ {
+ return $this->position;
+ }
+
+ #[\ReturnTypeWillChange]
+ public function next()
+ {
+ ++$this->position;
+ next($this->data[$this->dataset]);
+ }
+
+ #[\ReturnTypeWillChange]
+ public function rewind()
+ {
+ $this->position = 0;
+ reset($this->data[$this->dataset]);
+ }
+
+ #[\ReturnTypeWillChange]
+ public function valid()
+ {
+ return $this->position < $this->count;
+ }
+
+ /**
+ * Returns an item by index
+ */
+ public function getItemByIndex($index)
+ {
+ $slice = array_slice($this->data[$this->dataset], $index, 1, true);
+ // use foreach to get key and value
+ foreach($slice as $k => $v)
+ return new DataItem($k, $v);
+ return null;
+ }
+
+ /**
+ * Returns an item by its key
+ */
+ public function getItemByKey($key)
+ {
+ if(isset($this->data[$this->dataset][$key]))
+ return new DataItem($key, $this->data[$this->dataset][$key]);
+ return null;
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/DataLabels.php b/classes/vendor/81x/goat1000/svggraph/DataLabels.php
new file mode 100644
index 0000000..a78bf60
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/DataLabels.php
@@ -0,0 +1,1613 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class DataLabels {
+
+ protected $graph;
+ protected $have_filters = false;
+ private $labels = [];
+ private $max_values = [];
+ private $min_values = [];
+ private $start_indices = [];
+ private $end_indices = [];
+ private $peak_indices = [];
+ private $trough_indices = [];
+ private $directions = [];
+ private $last = [];
+ private $max_labels = 1000;
+ private $coords = null;
+
+ private $semantic_classes;
+ private $units_before;
+ private $units;
+ private $filter;
+ private $same_size;
+ private $callback;
+
+ /**
+ * Details of each label type
+ */
+ private $types_info = [
+ 'box' => ['shape' => 'boxLabel', 'tail' => false, 'pad' => true],
+ 'bubble' => ['shape' => 'bubbleLabel', 'tail' => true, 'pad' => true],
+ 'circle' => ['shape' => 'circleLabel', 'tail' => false, 'pad' => true],
+ 'line' => ['shape' => 'lineLabel', 'tail' => true, 'pad' => false],
+ 'line2' => ['shape' => 'lineLabel2', 'tail' => true, 'pad' => true],
+ 'linebox' => ['shape' => 'boxLineLabel', 'tail' => true, 'pad' => true],
+ 'linecircle' => ['shape' => 'circleLineLabel', 'tail' => true, 'pad' => true],
+ 'linesquare' => ['shape' => 'squareLineLabel', 'tail' => true, 'pad' => true],
+ 'plain' => ['shape' => null, 'tail' => false, 'pad' => false],
+ 'square' => ['shape' => 'squareLabel', 'tail' => false, 'pad' => true],
+ ];
+
+ /**
+ * Mapping between label style array members and options
+ */
+ protected $style_map = [
+ 'type' => 'data_label_type',
+ 'font' => 'data_label_font',
+ 'font_size' => 'data_label_font_size',
+ 'font_adjust' => 'data_label_font_adjust',
+ 'font_weight' => 'data_label_font_weight',
+ 'colour' => 'data_label_colour',
+ 'altcolour' => 'data_label_colour_outside',
+ 'back_colour' => 'data_label_back_colour',
+ 'back_altcolour' => 'data_label_back_colour_outside',
+ 'space' => 'data_label_space',
+ 'angle' => 'data_label_angle',
+ 'pad_x' => 'data_label_padding_x',
+ 'pad_y' => 'data_label_padding_y',
+ 'round' => 'data_label_round',
+ 'stroke' => 'data_label_outline_colour',
+ 'stroke_width' => 'data_label_outline_thickness',
+ 'fill' => 'data_label_fill',
+ 'tail_width' => 'data_label_tail_width',
+ 'tail_length' => 'data_label_tail_length',
+ 'tail_end' => 'data_label_tail_end',
+ 'tail_end_angle' => 'data_label_tail_end_angle',
+ 'tail_end_width' => 'data_label_tail_end_width',
+ 'shadow_opacity' => 'data_label_shadow_opacity',
+ 'opacity' => 'data_label_opacity',
+ 'line_spacing' => 'data_label_line_spacing',
+ 'align' => 'data_label_align',
+ ];
+
+ /**
+ * Options that are colours, so need translation
+ */
+ protected $colour_options = [
+ 'colour', 'altcolour', 'back_colour', 'back_altcolour', 'stroke', 'fill',
+ ];
+
+ function __construct(&$graph)
+ {
+ $this->graph =& $graph;
+ $this->filter = $graph->getOption('data_label_filter');
+ $this->have_filters = (!empty($this->filter) && $this->filter !== 'all');
+
+ $max_labels = $graph->getOption('data_label_max_count');
+ if($max_labels >= 1)
+ $this->max_labels = (int)$max_labels;
+ $this->semantic_classes = $graph->getOption('semantic_classes');
+ $this->units_before = $graph->getOption('units_before_label');
+ $this->units = $graph->getOption('units_label');
+ $this->same_size = $graph->getOption('data_label_same_size');
+ $this->callback = $graph->getOption('data_label_callback');
+ }
+
+ /**
+ * Adds a label to the list
+ */
+ public function addLabel($dataset, $index, &$item, $x, $y, $w, $h,
+ $id = null, $content = null, $fade_in = null, $click = null)
+ {
+ if(!isset($this->labels[$dataset]))
+ $this->labels[$dataset] = [];
+ $this->labels[$dataset][$index] = [
+ 'item' => $item, 'id' => $id, 'content' => $content,
+ 'x' => $x, 'y' => $y, 'width' => $w, 'height' => $h,
+ 'fade' => $fade_in, 'click' => $click,
+ ];
+
+ if($this->have_filters)
+ $this->setupFilters($dataset, $index, $item->value);
+ }
+
+ /**
+ * Adds a content (non-data) label
+ */
+ public function addContentLabel($dataset, $index, $x, $y, $w, $h, $content)
+ {
+ if(!isset($this->labels[$dataset]))
+ $this->labels[$dataset] = [];
+ $this->labels[$dataset][$index] = [
+ 'item' => null, 'id' => null, 'content' => $content,
+ 'x' => $x, 'y' => $y, 'width' => $w, 'height' => $h,
+ 'fade' => null, 'click' => null,
+ ];
+ }
+
+ /**
+ * Adds a user-defined label from a label option
+ */
+ public function addUserLabel($label_array)
+ {
+ if(!isset($this->labels['_user']))
+ $this->labels['_user'] = [];
+
+ if(!isset($label_array[0]) || !isset($label_array[1]) || !isset($label_array[2]))
+ throw new \Exception('Malformed label option - required fields missing');
+
+ $x = $label_array[0];
+ $y = $label_array[1];
+ $content = $label_array[2];
+ $w = 0;
+ $h = 0;
+ // merge the options with required fields
+ $this->labels['_user'][] = array_merge($label_array, [
+ 'item' => null, 'id' => null, 'content' => $content,
+ 'x' => $x, 'y' => $y, 'width' => $w, 'height' => $h,
+ 'fade' => null, 'click' => null,
+ ]);
+ }
+
+ /**
+ * Returns label details
+ */
+ public function getLabel($dataset, $index)
+ {
+ if(isset($this->labels[$dataset][$index]))
+ return $this->labels[$dataset][$index];
+ return null;
+ }
+
+ /**
+ * Updates filter information from label
+ */
+ protected function setupFilters($dataset, $index, $value)
+ {
+ // set up filtering info
+ if(!isset($this->max_values[$dataset]) ||
+ $this->max_values[$dataset] < $value)
+ $this->max_values[$dataset] = $value;
+ if(!isset($this->min_values[$dataset]) ||
+ $this->min_values[$dataset] > $value)
+ $this->min_values[$dataset] = $value;
+ if(!isset($this->start_indices[$dataset]) ||
+ $this->start_indices[$dataset] > $index)
+ $this->start_indices[$dataset] = $index;
+ if(!isset($this->end_indices[$dataset]) ||
+ $this->end_indices[$dataset] < $index)
+ $this->end_indices[$dataset] = $index;
+
+ // peaks and troughs are a bit more complicated
+ if(!isset($this->last[$dataset])) {
+ $this->last[$dataset] = [$index, $value];
+ $this->directions[$dataset] = null;
+ $this->peak_indices[$dataset] = [];
+ $this->trough_indices[$dataset] = [];
+ return;
+ }
+
+ if($this->last[$dataset][1] != $value) {
+ $last = $this->last[$dataset];
+ $diff = $value - $last[1];
+ $direction = ($diff > 0);
+ if($this->directions[$dataset] !== null &&
+ $direction !== $this->directions[$dataset]) {
+ if($diff > 0)
+ $this->trough_indices[$dataset][] = $last[0];
+ else
+ $this->peak_indices[$dataset][] = $last[0];
+ }
+ $this->last[$dataset] = [$index, $value];
+ $this->directions[$dataset] = $direction;
+ }
+ }
+
+
+ /**
+ * Load user-defined labels
+ */
+ public function load(&$settings)
+ {
+ if(!is_array($settings['label']) || !isset($settings['label'][0]))
+ throw new \Exception('Malformed label option');
+
+ if(!is_array($settings['label'][0])) {
+ $this->addUserLabel($settings['label']);
+ $this->coords = new Coords($this->graph);
+ return;
+ }
+ $count = 0;
+ foreach($settings['label'] as $label) {
+ $this->addUserLabel($label);
+ ++$count;
+ }
+ if($count)
+ $this->coords = new Coords($this->graph);
+ }
+
+ /**
+ * Returns all the labels as a string
+ */
+ public function getLabels()
+ {
+ $filter_count = is_array($this->filter) ? count($this->filter) : 1;
+ $label_list = [];
+ foreach($this->labels as $dataset => $label_set) {
+
+ $set_filter = 'all';
+ if(is_numeric($dataset)) {
+ $set_filter = is_array($this->filter) ?
+ $this->filter[$dataset % $filter_count] : $this->filter;
+ }
+ $count = 0;
+ foreach($label_set as $i => $label) {
+ if($this->filter($set_filter, $dataset, $label, $i)) {
+ $content = $this->getLabelText($dataset, $label);
+ if($content !== null && $content != '') {
+
+ list($w, $h) = $this->measureLabel($content, $dataset, $i, $label);
+
+ $label_list[] = compact('content', 'w', 'h', 'dataset', 'i', 'label');
+ if(++$count >= $this->max_labels)
+ break;
+ }
+ }
+ }
+ }
+
+ $this->setLabelSizes($label_list);
+ $labels = '';
+ foreach($label_list as $l) {
+ $labels .= $this->drawLabel($l['content'], $l['w'], $l['h'],
+ $l['dataset'], $l['i'], $l['label']);
+ }
+ if($labels != '') {
+ $group = [];
+ if($this->semantic_classes)
+ $group['class'] = 'data-labels';
+ $labels = $this->graph->element('g', $group, null, $labels);
+ }
+ return $labels;
+ }
+
+ /**
+ * Adjusts the label sizes
+ */
+ protected function setLabelSizes(&$labels)
+ {
+ if(!$this->same_size)
+ return;
+
+ // globally equal sizes
+ if(!is_array($this->same_size)) {
+ $max_w = 0;
+ $max_h = 0;
+ foreach($labels as $l) {
+ if(is_numeric($l['dataset'])) {
+ if($l['w'] > $max_w)
+ $max_w = $l['w'];
+ if($l['h'] > $max_h)
+ $max_h = $l['h'];
+ }
+ }
+
+ foreach($labels as $k => $l) {
+ if(is_numeric($l['dataset'])) {
+ $labels[$k]['w'] = $max_w;
+ $labels[$k]['h'] = $max_h;
+ }
+ }
+ return;
+ }
+
+ // per-dataset maxima (20 datasets should be enough for anybody)
+ $max_w = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
+ $max_h = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
+
+ foreach($labels as $l) {
+ $d = $l['dataset'];
+ if(is_numeric($d)) {
+ if($l['w'] > $max_w[$d])
+ $max_w[$d] = $l['w'];
+ if($l['h'] > $max_h[$d])
+ $max_h[$d] = $l['h'];
+ }
+ }
+
+ foreach($labels as $k => $l) {
+ $d = $l['dataset'];
+ if(is_numeric($d) &&
+ $this->graph->getOption(['data_label_same_size', $d])) {
+ $labels[$k]['w'] = $max_w[$d];
+ $labels[$k]['h'] = $max_h[$d];
+ }
+ }
+ }
+
+ /**
+ * Returns the text for a label
+ */
+ protected function getLabelText($dataset, &$gobject)
+ {
+ if($gobject['item'] === null && empty($gobject['content']))
+ return '';
+
+ if($gobject['item'] === null)
+ return (string)$gobject['content'];
+
+ if(is_callable($this->callback)) {
+ $content = call_user_func($this->callback, $dataset,
+ $gobject['item']->key, $gobject['item']->value);
+ return $content === null ? '' : $content;
+ }
+
+ $content = $gobject['item']->label;
+ if($content !== null)
+ return $content;
+
+ if($gobject['content'] !== null)
+ return $gobject['content'];
+
+ $n = new Number($gobject['item']->value, $this->units, $this->units_before);
+ return $n->format();
+ }
+
+ /**
+ * Returns the style details for a label
+ */
+ protected function getStyle($dataset, $index, &$gobject)
+ {
+ // global styles filled in by graph class
+ $style = $this->graph->dataLabelStyle($dataset, $index, $gobject['item']);
+
+ // structured styles and user defined label styles
+ if($gobject['item'] !== null)
+ $this->itemStyles($style, $gobject['item'], $index, $dataset);
+ elseif($dataset === '_user')
+ $this->userStyles($style, $gobject);
+
+ // deal with fill/fillColour
+ foreach($this->colour_options as $s) {
+ if(isset($style[$s]) && is_string($style[$s]) &&
+ strpos($style[$s], 'fill') !== false) {
+ $cg = new ColourGroup($this->graph, $gobject['item'], $index, $dataset,
+ $style[$s], null, null, true);
+ $style[$s] = $cg->stroke();
+ }
+ }
+ return $style;
+ }
+
+ /**
+ * Returns the font size and line spacing for a style as an array
+ */
+ protected function getFontSize($style)
+ {
+ $font_size = $line_spacing = max(4, Number::units($style['font_size']));
+ if($style['line_spacing'] !== null)
+ $line_spacing = max(1, Number::units($style['line_spacing']));
+ return [$font_size, $line_spacing];
+ }
+
+ /**
+ * Returns size of a label as array (w, h)
+ */
+ protected function measureLabel($content, $dataset, $index, &$gobject)
+ {
+ $style = $this->getStyle($dataset, $index, $gobject);
+
+ // get size of text
+ list($font_size, $line_spacing) = $this->getFontSize($style);
+ $svg_text = new Text($this->graph, $style['font'], $style['font_adjust']);
+ list($w, $h) = $svg_text->measure($content, $font_size, $style['angle'],
+ $line_spacing);
+
+ // if this label type uses padding, add it in
+ if($this->getTypeInfo($style['type'], 'pad')) {
+ $w += $style['pad_x'] * 2;
+ $h += $style['pad_y'] * 2;
+ }
+
+ return [$w, $h];
+ }
+
+ /**
+ * Returns some or all info about a type
+ */
+ protected function getTypeInfo($type, $field = null)
+ {
+ if(!isset($this->types_info[$type]))
+ $type = 'plain';
+ $type_info = $this->types_info[$type];
+ return $field === null ? $type_info : $type_info[$field];
+ }
+
+ /**
+ * Returns the foreground and background colours, dependent on relative
+ * position
+ */
+ protected function getColours($hpos, $vpos, $style)
+ {
+ // if the position is outside, use the alternative colours
+ $colour = new Colour($this->graph, $style['colour']);
+ $back_colour = new Colour($this->graph, $style['back_colour']);
+ if(strpos($hpos . $vpos, 'o') !== false) {
+ $alt = new Colour($this->graph, $style['altcolour']);
+ $back_alt = new Colour($this->graph, $style['back_altcolour']);
+ if(!$alt->isNone())
+ $colour = $alt;
+ if(!$back_alt->isNone())
+ $back_colour = $back_alt;
+ }
+ return [$colour, $back_colour];
+ }
+
+ /**
+ * Draws a label
+ */
+ protected function drawLabel($content, $label_w, $label_h, $dataset, $index,
+ &$gobject)
+ {
+ $style = $this->getStyle($dataset, $index, $gobject);
+ $style['target'] = [$gobject['x'], $gobject['y']];
+
+ $space = (float)$style['space'];
+ $pos = null;
+ if($dataset === '_user') {
+ // user label, so convert coordinates
+ $pos = isset($gobject['position']) ? $gobject['position'] : 'above';
+ $xy = $this->coords->transformCoords($gobject['x'], $gobject['y']);
+ $gobject['x'] = $xy[0];
+ $gobject['y'] = $xy[1];
+ $style['target'] = [$gobject['x'], $gobject['y']];
+ } else {
+ // try to get position from item
+ if($gobject['item'] !== null)
+ $pos = $gobject['item']->data_label_position;
+
+ // find out from graph class where this label should go
+ if($pos === null) {
+ $label_wp = $label_w + $space * 2;
+ $label_hp = $label_h + $space * 2;
+
+ // get the label position and the target for tail
+ list($pos, $target) = $this->graph->dataLabelPosition($dataset,
+ $index, $gobject['item'], $gobject['x'], $gobject['y'],
+ $gobject['width'], $gobject['height'], $label_wp, $label_hp);
+ $style['target'] = $target;
+ }
+ }
+
+ // convert position string to an actual location
+ list($x, $y, $anchor, $hpos, $vpos) = Graph::relativePosition($pos,
+ $gobject['y'], $gobject['x'],
+ $gobject['y'] + $gobject['height'], $gobject['x'] + $gobject['width'],
+ $label_w, $label_h, $space, true);
+
+ list($colour, $back_colour) = $this->getColours($hpos, $vpos, $style);
+ list($font_size, $line_spacing) = $this->getFontSize($style);
+ $text = [
+ 'font-family' => $style['font'],
+ 'font-size' => $font_size,
+ 'fill' => $colour,
+ ];
+
+ $label_markup = '';
+ $label_pad_x = $label_pad_y = 0;
+ $type_info = $this->getTypeInfo($style['type']);
+ if($type_info['pad']) {
+ $label_pad_x = $style['pad_x'];
+ $label_pad_y = $style['pad_y'];
+ }
+
+ // need text size without padding, rotation, etc.
+ $svg_text = new Text($this->graph, $style['font'], $style['font_adjust']);
+ list($tbw, $tbh) = $svg_text->measure($content, $font_size, 0, $line_spacing);
+ $text_baseline = $svg_text->baseline($font_size);
+
+ // allow overriding text alignment
+ $align_map = ['left' => 'start', 'centre' => 'middle', 'right' => 'end'];
+ if($style['align'] !== null && isset($align_map[$style['align']])) {
+ $anchor_new = $align_map[$style['align']];
+ if($anchor_new != $anchor) {
+ // adjust label position for new anchor
+ $pos_remap = [
+ 'middle' => ['start' => -0.5, 'end' => 0.5],
+ 'start' => ['middle' => 0.5, 'end' => 1.0],
+ 'end' => ['start' => -1.0, 'middle' => -0.5],
+ ];
+
+ $x += ($label_w * $pos_remap[$anchor][$anchor_new]);
+ $anchor = $anchor_new;
+ }
+ }
+
+ $text['y'] = $y + ($label_h - $tbh) / 2 + $text_baseline;
+ if($style['angle'] != 0) {
+
+ if($anchor == 'middle') {
+ $text['x'] = $x;
+ } elseif($anchor == 'start') {
+ $text['x'] = $x + ($label_w - $tbw) / 2;
+ } else {
+ $text['x'] = $x - ($label_w - $tbw) / 2;
+ }
+
+ } else {
+
+ if($anchor == 'start') {
+ $text['x'] = $x + $label_pad_x;
+ } elseif($anchor == 'end') {
+ $text['x'] = $x - $label_pad_x;
+ } else {
+ $text['x'] = $x;
+ }
+ }
+
+ // make x right for bounding box
+ if($anchor == 'middle') {
+ $x -= $label_w / 2;
+ } elseif($anchor == 'end') {
+ $x -= $label_w;
+ }
+
+ if($style['angle'] != 0) {
+ // rotate text around centre of box
+ $rx = $x + $label_w / 2;
+ $ry = $y + $label_h / 2;
+ $xform = new Transform;
+ $xform->rotate($style['angle'], $rx, $ry);
+ $text['transform'] = $xform;
+ }
+
+ if($anchor != 'start')
+ $text['text-anchor'] = $anchor;
+ if(!empty($style['font_weight']) && $style['font_weight'] != 'normal')
+ $text['font-weight'] = $style['font_weight'];
+
+ $surround = [];
+ $element = null;
+ $shape_func = null;
+ $need_tail = false;
+
+ if($type_info['shape']) {
+ if($type_info['tail']) {
+ $style['tail_direction'] = $this->graph->dataLabelTailDirection($dataset,
+ $index, $hpos, $vpos);
+ }
+
+ // make the shape
+ $element = $this->{$type_info['shape']}($x, $y, $label_w, $label_h,
+ $style, $surround);
+ if($element) {
+ $surround['stroke'] = new Colour($this->graph, $style['stroke']);
+ if($style['stroke_width'] != 1)
+ $surround['stroke-width'] = (float)$style['stroke_width'];
+
+ // add shadow if not completely transparent
+ if($style['shadow_opacity'] > 0) {
+ $shadow = $surround;
+ $offset = 2 + floor($style['stroke_width'] / 2);
+ $xform = new Transform;
+ $xform->translate($offset, $offset);
+ $shadow['transform'] = $xform;
+ $shadow['fill'] = $shadow['stroke'] = '#000';
+ $shadow['opacity'] = $style['shadow_opacity'];
+ $label_markup .= $this->graph->element($element, $shadow);
+ }
+ $label_markup .= $this->graph->element($element, $surround);
+ }
+ }
+
+ //$back_colour = new Colour($this, $back_colour);
+ if(!$back_colour->isNone()) {
+ $outline = [
+ 'stroke-width' => '3px',
+ 'stroke' => $back_colour,
+ 'stroke-linejoin' => 'round',
+ ];
+ $t1 = array_merge($outline, $text);
+ $label_markup .= $svg_text->text($content, $line_spacing, $t1);
+ }
+ $label_markup .= $svg_text->text($content, $line_spacing, $text);
+
+ $group = [];
+ if(isset($gobject['id']) && $gobject['id'] !== null)
+ $group['id'] = $gobject['id'];
+
+ // opacity is required when set or using click-show-hide
+ $opacity = max(0, min(1, $style['opacity']));
+ if($opacity < 1 || $gobject['click'] == 'show')
+ $group['opacity'] = $opacity;
+ elseif($gobject['click'] == 'hide' || $gobject['fade'])
+ $group['opacity'] = 0;
+
+ $label_markup = $this->graph->element('g', $group, null, $label_markup);
+ return $label_markup;
+ }
+
+ /**
+ * Returns the mapping between style members and option names
+ */
+ public function getStyleMap()
+ {
+ return $this->style_map;
+ }
+
+ /**
+ * Individual label styles from the structured data item
+ */
+ protected function itemStyles(&$style, &$item, $index, $dataset)
+ {
+ // overwrite any style options that the item has set
+ $v = $item->data_label_padding;
+ if($v !== null)
+ $style['pad_x'] = $style['pad_y'] = $v;
+ foreach($this->style_map as $s => $k) {
+ $v = $item->data($k);
+ if($v !== null)
+ $style[$s] = $v;
+ }
+ }
+
+ /**
+ * Styles from the label option
+ */
+ protected function userStyles(&$style, &$label_array)
+ {
+ // pad_x and pad_y will override single padding option
+ if(isset($label_array['padding']))
+ $style['pad_x'] = $style['pad_y'] = $label_array['padding'];
+ foreach($this->style_map as $s => $k) {
+ // remove the 'data_label_' part
+ $o = substr($k, 11);
+ if(isset($label_array[$o]))
+ $style[$s] = $label_array[$o];
+ }
+ }
+
+ /**
+ * Returns TRUE if the label should be shown
+ */
+ protected function filter($filter, $dataset, &$label, $index)
+ {
+ // non-numeric datasets are for additional labels,
+ // empty filter does nothing
+ if(!is_numeric($dataset) || empty($filter))
+ return true;
+
+ $item =& $label['item'];
+
+ // if the item has a show_label member, use it
+ $struct_show = $item->show_label;
+ if($struct_show !== null)
+ return $struct_show;
+
+ // if 'all' is in the list, others don't matter
+ $filters = explode(' ', $filter);
+ if(in_array('all', $filters, true))
+ return true;
+
+ // an array of closures for filter tests
+ $tests = [
+ 'start' => function() use ($index, $dataset) {
+ return $index == $this->start_indices[$dataset]; },
+ 'end' => function() use ($index, $dataset) {
+ return $index == $this->end_indices[$dataset]; },
+ 'max' => function() use (&$item, $dataset) {
+ return $item->value == $this->max_values[$dataset]; },
+ 'min' => function() use (&$item, $dataset) {
+ return $item->value == $this->min_values[$dataset]; },
+ 'peaks' => function() use ($index, $dataset) {
+ return in_array($index, $this->peak_indices[$dataset], true); },
+ 'troughs' => function() use ($index, $dataset) {
+ return in_array($index, $this->trough_indices[$dataset], true); },
+ 'nonzero' => function() use (&$item) { return $item->value != 0; },
+ 'none' => function() use (&$item) {
+ return $item->label !== null && $item->label !== ''; },
+ ];
+
+ foreach($filters as $f) {
+
+ if(isset($tests[$f]) && $tests[$f]())
+ return true;
+
+ // integer step
+ if(is_numeric($f) && $index % (int)$f == 0) {
+ return true;
+ } else {
+ // step with offset
+ $parts = explode('+', $f);
+ if(count($parts) == 2 &&
+ is_numeric($parts[0]) && is_numeric($parts[1]) &&
+ $parts[0] > 1 && $parts[1] < $parts[0] &&
+ $index % (int)$parts[0] == $parts[1])
+ return true;
+ }
+ }
+
+ // default is to show nothing
+ return false;
+ }
+
+
+ /**
+ * Straight line label style
+ */
+ protected function lineLabel($x, $y, $w, $h, &$style, &$surround)
+ {
+ $w2 = $w / 2;
+ $h2 = $h / 2;
+ $a = $style['tail_direction'] * M_PI / 180;
+
+ if($style['round']) {
+ $bbradius = sqrt($w2 * $w2 + $h2 * $h2);
+ $w2a = $bbradius * cos($a);
+ $h2a = $bbradius * sin($a);
+ } else {
+ // start at edge of text bounding box
+ $w2a = $w2;
+ $h2a = $w2 * tan($a);
+ if(abs($h2a) > $h2) {
+ $h2a = $h2;
+ $w2a = $h2 / tan($a);
+ }
+ }
+ if(($a < M_PI && $h2a < 0) || ($a > M_PI && $h2a > 0)) {
+ $h2a = -$h2a;
+ $w2a = -$w2a;
+ }
+
+ $x1 = $x + $w2 + $w2a;
+ $y1 = $y + $h2 + $h2a;
+ if($style['tail_length'] == 'auto') {
+ list($x2, $y2) = $style['target'];
+ // check line is outside bbox
+ if($style['round']) {
+ $llen = sqrt(pow($x2 - $x - $w2, 2) + pow($y2 - $y - $h2, 2));
+ if($llen < $bbradius)
+ return '';
+ } else {
+ if($x2 > $x && $x2 < $x + $w && $y2 > $y && $y2 < $y + $h)
+ return '';
+ }
+ } else {
+ // make sure line is long enough to not look like part of text
+ list($font_size) = $this->getFontSize($style);
+ $llen = max($font_size, $style['tail_length']);
+ $x2 = $x1 + ($llen * cos($a));
+ $y2 = $y1 + ($llen * sin($a));
+ }
+ $surround['d'] = new PathData('M', $x1, $y1, 'L', $x2, $y2);
+ return 'path';
+ }
+
+ /**
+ * Simple box label style
+ */
+ protected function boxLabel($x, $y, $w, $h, &$style, &$surround)
+ {
+ $surround['x'] = $x;
+ $surround['y'] = $y;
+ $surround['width'] = $w;
+ $surround['height'] = $h;
+ if($style['round'])
+ $surround['rx'] = $surround['ry'] = min((float)$style['round'],
+ $h / 2, $w / 2);
+ $surround['fill'] = new Colour($this->graph, $style['fill']);
+ return 'rect';
+ }
+
+ /**
+ * Speech bubble label style
+ */
+ protected function bubbleLabel($x, $y, $w, $h, &$style, &$surround)
+ {
+ // can't be more round than this!
+ $round = min((float)$style['round'], $h / 3, $w / 3);
+ $drop = max(2, (float)$style['tail_length']);
+ $spread = min(max(2, (float)$style['tail_width']), $w - $round * 2);
+
+ $vert = $h - $round * 2;
+ $horz = $w - $round * 2;
+ $start = new PathData('M', ($x + $w - $round), $y);
+ $t = new PathData('z');
+ $r = new PathData('v', $vert);
+ $b = new PathData('h', -$horz);
+ $l = new PathData('v', -$vert);
+ $tr = new PathData;
+ $br = new PathData;
+ $bl = new PathData;
+ $tl = new PathData;
+ if($round) {
+ $tr->add('a', $round, $round, 90, 0, 1, $round, $round);
+ $br->add('a', $round, $round, 90, 0, 1, -$round, $round);
+ $bl->add('a', $round, $round, 90, 0, 1, -$round, -$round);
+ $tl->add('a', $round, $round, 90, 0, 1, $round, -$round);
+ }
+
+ $direction = floor(($style['tail_direction'] + 22.5) * 8 / 360) % 8;
+ $ddrop = 0.707 * $drop; // cos 45
+ $p1 = $ddrop + $spread * 0.707;
+ $s2 = $spread / 2;
+ $vcropped = $vert - $spread * 0.707 + $round;
+ $hcropped = $horz - $spread * 0.707 + $round;
+ switch($direction) {
+ case 0 :
+ $bside = $h / 2 - $s2 - $round;
+ $r = new PathData('v', $bside, 'l', $drop, $s2, 'l', -$drop, $s2, 'v', $bside);
+ break;
+ case 1 :
+ $r = new PathData('v', $vcropped);
+ $br = new PathData('l', $ddrop, $p1, 'l', -$p1, -$ddrop);
+ $b = new PathData('h', -$hcropped);
+ break;
+ case 2 :
+ $bside = $w / 2 - $s2 - $round;
+ $b = new PathData('h', -$bside, 'l', -$s2, $drop, 'l', -$s2, -$drop, 'h', -$bside);
+ break;
+ case 3 :
+ $l = new PathData('v', -$vcropped);
+ $bl = new PathData('l', -$p1, $ddrop, 'l', $ddrop, -$p1);
+ $b = new PathData('h', -$hcropped);
+ break;
+ case 4 :
+ $bside = $h / 2 - $s2 - $round;
+ $l = new PathData('v', -$bside, 'l', -$drop, -$s2, 'l', $drop, -$s2, 'v', -$bside);
+ break;
+ case 5 :
+ $l = new PathData('v', -$vcropped);
+ $tl = new PathData('l', -$ddrop, -$p1, 'l', $p1, $ddrop);
+ break;
+ case 6 :
+ $bside = $w / 2 - $s2 - $round;
+ $t = new PathData('h', $bside, 'l', $s2, -$drop, 'l', $s2, $drop, 'z');
+ break;
+ case 7 :
+ $start = new PathData('M', ($x + $hcropped + $round), $y);
+ $r = new PathData('v', $vcropped);
+ $tr = new PathData('l', $p1, -$ddrop, 'l', -$ddrop, $p1);
+ break;
+ }
+ $start->add($tr);
+ $start->add($r);
+ $start->add($br);
+ $start->add($b);
+ $start->add($bl);
+ $start->add($l);
+ $start->add($tl);
+ $start->add($t);
+ $surround['d'] = $start;
+ $surround['fill'] = new Colour($this->graph, $style['fill']);
+ return 'path';
+ }
+
+ /**
+ * Returns the cx, cy and radius for a round label
+ */
+ protected function calcRoundLabel($x, $y, $w, $h)
+ {
+ $w2 = $w / 2;
+ $h2 = $h / 2;
+ $r = sqrt($w2 * $w2 + $h2 * $h2);
+ return [$x + $w2, $y + $h2, $r];
+ }
+
+ /**
+ * Circular label style
+ */
+ protected function circleLabel($x, $y, $w, $h, &$style, &$surround)
+ {
+ $params = $this->calcRoundLabel($x, $y, $w, $h);
+ $surround['cx'] = $params[0];
+ $surround['cy'] = $params[1];
+ $surround['r'] = $params[2];
+ $surround['fill'] = new Colour($this->graph, $style['fill']);
+ return 'circle';
+ }
+
+ /**
+ * Returns the tail target coordinates, angle and length for a box label,
+ * or NULL if the tail would end inside the label.
+ * Return value is array(array($x, $y), $angle, $length)
+ */
+ protected function getBoxTailTarget(&$style, $x1, $y1, $x2, $y2)
+ {
+ $target = null;
+ $length = $style['tail_length'];
+ $cx = ($x1 + $x2) / 2;
+ $cy = ($y1 + $y2) / 2;
+ $w2 = $cx - $x1;
+ $h2 = $cy - $y1;
+ if($length == 'auto') {
+ // just use the defined target
+ $target = $style['target'];
+
+ // check that target is outside the label
+ $tx = $target[0]; $ty = $target[1];
+ if($tx >= $x1 && $tx <= $x2 && $ty >= $y1 && $ty <= $y2)
+ return null;
+
+ if($tx > $x2) {
+ $dx = $tx - $x2;
+ } elseif($tx < $x1) {
+ $dx = $x1 - $tx;
+ } else {
+ $dx = abs($tx - $cx);
+ }
+ if($ty > $y2) {
+ $dy = $ty - $y2;
+ } elseif($ty < $y1) {
+ $dy = $y1 - $ty;
+ } else {
+ $dy = abs($ty - $cy);
+ }
+ $length = sqrt($dx * $dx + $dy * $dy);
+ $angle = atan2($ty - $cy, $tx - $cx);
+ } else {
+ // target is radius + tail length away
+ if($length <= 0)
+ return null;
+ $angle = $style['tail_direction'] * M_PI / 180;
+
+ $lx = $length * cos($angle);
+ $ly = $length * sin($angle);
+ // compare tangent with box ratio
+ if(abs(tan($angle)) > $h2 / $w2) {
+ // out top or bottom
+ $target = [
+ $cx + $lx,
+ $ly > 0 ? $y2 + $ly : $y1 + $ly
+ ];
+ } else {
+ // out left or right
+ $target = [
+ $lx > 0 ? $x2 + $lx : $x1 + $lx,
+ $cy + $ly
+ ];
+ }
+ }
+ return [$target, $angle, $length];
+ }
+
+ /**
+ * Returns the tail target coordinates, angle and length for a round label,
+ * or NULL if the tail would end inside the label.
+ * Return value is array(array($x, $y), $angle, $length)
+ */
+ protected function getRoundTailTarget(&$style, $cx, $cy, $radius)
+ {
+ $target = null;
+ $length = $style['tail_length'];
+ if($length == 'auto') {
+ // just use the defined target
+ $target = $style['target'];
+
+ // check that target is outside the label radius
+ $tx = $target[0] - $cx;
+ $ty = $target[1] - $cy;
+ $rt = sqrt($tx * $tx + $ty * $ty);
+ if($rt <= $radius)
+ return null;
+ $angle = atan2($ty, $tx);
+ $length = $rt - $radius;
+ } else {
+ // target is radius + tail length away
+ if($length <= 0)
+ return null;
+ $len = $radius + $length;
+ $angle = $style['tail_direction'] * M_PI / 180;
+ $target = [$cx + ($len * cos($angle)), $cy + ($len * sin($angle))];
+ }
+ return [$target, $angle, $length];
+ }
+
+ /**
+ * Rotate and translate $x and $y, returning Point
+ */
+ private static function xForm($x, $y, $a, $tx, $ty)
+ {
+ if($x == 0 && $y == 0)
+ return new Point($tx, $ty);
+ $sa = sin($a);
+ $ca = cos($a);
+ $x1 = $x * $ca - $y * $sa;
+ $y1 = $x * $sa + $y * $ca;
+ return new Point($tx + $x1, $ty + $y1);
+ }
+
+ /**
+ * Returns the tail ending path fragment
+ */
+ protected function getTailEnding($x, $y, $langle, $lwidth, $dist, &$style)
+ {
+ $a = max(5, min(80, $style['tail_end_angle']));
+ $ewidth = max($lwidth, $style['tail_end_width']);
+ $eangle = M_PI * $a / 180;
+
+ // first fallback is a tapering line
+ $fallback = new PathData('L', $x, $y);
+ $lw = $lwidth * 0.5;
+ $ew = $ewidth * 0.5;
+ $ll = $lw / tan($eangle);
+ $el = $ew / tan($eangle);
+
+ // ends are defined pointing upwards
+ $langle -= M_PI * 0.5;
+ $type = $style['tail_end'];
+
+ // 'point' style by default
+ $points = [ [-$lw, -$ll], [0, 0], [$lw, -$ll] ];
+
+ switch($type)
+ {
+ default:
+ case 'flat' :
+ $points = [ [-$lw, 0], [$lw, 0] ];
+ break;
+ case 'taper' :
+ return $fallback;
+ case 'point' :
+ if($dist <= $ll)
+ return $fallback;
+ break;
+
+ case 'filled' :
+ if($dist <= $el)
+ return $fallback;
+ if($ew > $lw) {
+ $points = [
+ [-$lw, -$el], [-$ew, -$el], [0, 0], [$ew, -$el], [$lw, -$el]
+ ];
+ }
+ break;
+ case 'arrow' :
+ $tip_w = $lwidth * sin($eangle);
+ $w1 = $ew - $tip_w;
+ $l2 = $el + $lwidth * cos($eangle);
+ $l1 = $l2 - ($ew - $tip_w - $lw) / tan($eangle);
+ if($dist < $l2)
+ return $fallback;
+ if($w1 > $lw) {
+ $points = [
+ [-$lw, -$l1], [-$w1, -$l2], [-$ew, -$el],
+ [0, 0],
+ [$ew, -$el], [$w1, -$l2], [$lw, -$l1]
+ ];
+ break;
+ }
+ // fall through to diamond shape if not wide enough
+
+ case 'diamond' :
+ $blen = 2 * $el - $ll;
+ if($dist <= $blen)
+ return $fallback;
+ if($ew > $lw) {
+ $points = [
+ [-$lw, -$blen], [-$ew, -$el], [0,0], [$ew, -$el], [$lw, -$blen]
+ ];
+ }
+ break;
+ case 'tee' :
+ if($dist <= $lwidth)
+ return $fallback;
+ $points = [
+ [-$lw, -$lwidth], [-$ew, -$lwidth], [-$ew, 0],
+ [$ew, 0], [$ew, -$lwidth], [$lw, -$lwidth]
+ ];
+ break;
+ case 'round' :
+ if($dist < $lw)
+ return $fallback;
+ $cradius = min($ew, max($lw, $dist / 2));
+ $rlen = sqrt(($cradius * $cradius) - ($lw * $lw));
+ $rdist = $cradius + $rlen;
+ $p1 = $this->xForm(-$lw, -$rdist, $langle, $x, $y);
+ $p2 = $this->xForm($lw, -$rdist, $langle, $x, $y);
+ return new PathData('L', $p1, 'A', $cradius, $cradius, 0, 1, 0, $p2);
+ }
+
+ $path = new PathData;
+ foreach($points as $pair) {
+ $pt = $this->xForm($pair[0], $pair[1], $langle, $x, $y);
+ $path->add('L', $pt);
+ }
+ return $path;
+ }
+
+ /**
+ * Returns the point where a line crosses an arc
+ */
+ private function lineCrossArc($x, $y, $angle, $cx, $cy, $radius, $corner)
+ {
+ $h = $cx;
+ $k = $cy;
+ $r = $radius;
+
+ // 90-degree angles are simpler
+ $cos = abs(cos($angle));
+ $sin = abs(sin($angle));
+ if($cos == 1 || $sin == 1) {
+ if($cos == 1) {
+ $rt = sqrt(-($y * $y) + (2 * $y * $k) - ($k * $k) + ($r * $r));
+ $y1 = $y2 = $y;
+ $x1 = $h - $rt;
+ $x2 = $h + $rt;
+ } else {
+ $rt = sqrt(-($x * $x) + (2 * $x * $h) - ($h * $h) + ($r * $r));
+ $x1 = $x2 = $x;
+ $y1 = $k - $rt;
+ $y2 = $k + $rt;
+ }
+ } else {
+ // y = mx + c
+ $m = tan($angle);
+ $c = $y - ($x * $m);
+
+ // using quadratic formula
+ $disc = -($c * $c) - (2 * $c * $h * $m) + (2 * $c * $k)
+ - ($h * $h * $m * $m) + (2 * $h * $k * $m) - ($k * $k)
+ + ($m * $m * $r * $r) + ($r * $r);
+ $rt = sqrt($disc);
+ $b = (-$c * $m) + $h + ($k * $m);
+ $d = ($m * $m) + 1;
+
+ $x1 = (-$rt + $b) / $d;
+ $x2 = ($rt + $b) / $d;
+
+ // y = mx + c again, using original x and y
+ $y1 = $m * $x1 + $c;
+ $y2 = $m * $x2 + $c;
+ }
+
+ $use_first = false;
+ switch($corner) {
+ case 'tr' :
+ if($x1 > $cx && $y1 < $cy)
+ $use_first = true;
+ break;
+ case 'tl' :
+ if($x1 < $cx && $y1 < $cy)
+ $use_first = true;
+ break;
+ case 'br' :
+ if($x1 > $cx && $y1 > $cy)
+ $use_first = true;
+ break;
+ case 'bl' :
+ if($x1 < $cx && $y1 > $cy)
+ $use_first = true;
+ }
+ if($use_first)
+ return new Point($x1, $y1);
+ return new Point($x2, $y2);
+ }
+
+ /**
+ * Filled line label
+ */
+ protected function lineLabel2($x, $y, $w, $h, &$style, &$surround)
+ {
+ if($style['round']) {
+ list($cx, $cy, $bbradius) = $this->calcRoundLabel($x, $y, $w, $h);
+ list($target, $angle, $len) = $this->getRoundTailTarget($style, $cx, $cy,
+ $bbradius);
+ } else {
+ list($target, $angle, $len) = $this->getBoxTailTarget($style, $x, $y, $x + $w,
+ $y + $h);
+ }
+ if($target === null)
+ return null;
+
+ if($style['round']) {
+ $t_width = max(1, min($style['tail_width'], $bbradius));
+ $l_angle = asin($t_width * 0.5 / $bbradius);
+ $p1 = new Point($cx + $bbradius * cos($angle - $l_angle),
+ $cy + $bbradius * sin($angle - $l_angle));
+ $p2 = new Point($cx + $bbradius * cos($angle + $l_angle),
+ $cy + $bbradius * sin($angle + $l_angle));
+ } else {
+
+ $h2 = $h * 0.5; $w2 = $w * 0.5;
+ $cx = $x + $w2; $cy = $y + $h2;
+ $t_width = max(1, min((float)$style['tail_width'], $w - 1, $h - 1));
+ $sin = sin($angle);
+ $cos = cos($angle);
+ $xo = $yo = 0;
+ if(abs($sin) == 1) {
+ $py = $cy + $h2 * $sin;
+ $px = $cx;
+ $xo = $t_width * 0.5;
+ } elseif(abs($cos) == 1) {
+ $px = $cx + $w2 * $cos;
+ $py = $cy;
+ $yo = $t_width * 0.5;
+ } else {
+ $h1 = abs($w2 * tan($angle));
+ if($h1 >= $h2) {
+ $h1 = ($sin < 0 ? -$h2 : $h2);
+ $w1 = $h1 * tan(M_PI * 0.5 - $angle);
+ } else {
+ $w1 = ($cos < 0 ? -$w2 : $w2);
+ $h1 = $w1 / tan(M_PI * 0.5 - $angle);
+ }
+ $px = $cx + $w1;
+ $py = $cy + $h1;
+ $xo = $t_width * 0.5 * $sin;
+ $yo = $t_width * -0.5 * $cos;
+ }
+
+ $p1 = new Point($px + $xo, $py + $yo);
+ $p2 = new Point($px - $xo, $py - $yo);
+ $l1 = $px - $target[0];
+ $l2 = $py - $target[1];
+ $len = sqrt($l1 * $l1 + $l2 * $l2);
+ }
+
+ $surround['fill'] = new Colour($this->graph, $style['fill']);
+ $path = new PathData('M', $p1, 'L', $p2);
+ $path->add($this->getTailEnding($target[0], $target[1], $angle, $t_width,
+ $len, $style));
+ $path->add('z');
+ $surround['d'] = $path;
+ return 'path';
+ }
+
+ /**
+ * Line and box label style
+ */
+ protected function boxLineLabel($x, $y, $w, $h, &$style, &$surround)
+ {
+ $x1 = $x; $y1 = $y;
+ $x2 = $x + $w; $y2 = $y + $h;
+ list($target, $angle, $len) = $this->getBoxTailTarget($style, $x1, $y1,
+ $x2, $y2);
+ if($target === null)
+ return $this->boxLabel($x, $y, $w, $h, $style, $surround);
+
+ $round = min((float)$style['round'], $h / 3, $w / 3);
+ $t_width = max(1, min((float)$style['tail_width'], $w - 1, $h - 1));
+ $cx = ($x1 + $x2) / 2;
+ $cy = ($y1 + $y2) / 2;
+
+ while($angle < 0)
+ $angle += M_PI * 2.0;
+
+ // centre points of corner arcs
+ $x1c = $x1 + $round;
+ $x2c = $x2 - $round;
+ $y1c = $y1 + $round;
+ $y2c = $y2 - $round;
+
+ // box corners
+ if($round) {
+ $arc = new PathData('a', $round, $round, 0, 0, 0);
+ $c_tl = new PathData('L', $x1c, $y1);
+ $c_tl->add($arc);
+ $c_tl->add(-$round, $round);
+ $c_tr = new PathData('L', $x2, $y1c);
+ $c_tr->add($arc);
+ $c_tr->add(-$round, -$round);
+ $c_bl = new PathData('L', $x1, $y2c);
+ $c_bl->add($arc);
+ $c_bl->add($round, $round);
+ $c_br = new PathData('L', $x2c, $y2);
+ $c_br->add($arc);
+ $c_br->add($round, -$round);
+ // this gets repeated a lot
+ $arc = new PathData('A', $round, $round, 0, 0, 0);
+ } else {
+ $c_tl = new PathData('L', $x1, $y1);
+ $c_tr = new PathData('L', $x2, $y1);
+ $c_bl = new PathData('L', $x1, $y2);
+ $c_br = new PathData('L', $x2, $y2);
+ }
+ $points = [];
+ $rangle = M_PI * 0.5 - $angle;
+ if(abs(tan($angle)) > $h / $w) {
+ // top or bottom
+ // $hoff = horizontal offset from centre of edge
+ // $wa = width at angle
+ $hoff = $h * 0.5 * tan($rangle);
+ $wa = $t_width * 0.5 / cos($rangle);
+ if($angle > M_PI) {
+ // top
+ $p1 = new Point($cx - $hoff + $wa, $y1);
+ if($p1->x < $x1) {
+ $p1->y = $y1 + ($x1 - $p1->x) * tan($angle);
+ $p1->x = $x1;
+ }
+ $p2 = new Point($cx - $hoff - $wa, $y1);
+ if($p2->x > $x2) {
+ $p2->y = $y1 - ($p2->x - $x2) * tan($angle);
+ $p2->x = $x2;
+ }
+ $start = new PathData('M', $p1);
+ $end = new PathData('L', $p2);
+ // if the line meets the side past the corner radius, there is no corner
+ if($p1->y > $y1c)
+ $c_tl->clear();
+ if($p2->y > $y1c)
+ $c_tr->clear();
+ if($round) {
+ if(!$c_tl->isEmpty() && $p1->x < $x1c) {
+ $cross = $this->lineCrossArc($p1->x, $p1->y, $angle, $x1c, $y1c, $round, 'tl');
+ $start = new PathData('M');
+ $start->add($cross);
+ $c_tl = new PathData($arc);
+ $c_tl->add($x1, $y1c);
+ if($p2->x < $x1c) {
+ $cross = $this->lineCrossArc($p2->x, $p2->y, $angle, $x1c, $y1c, $round, 'tl');
+ $end = new PathData('L', $x1c, $y1);
+ $end->add($arc);
+ $end->add($cross);
+ }
+ }
+ if(!$c_tr->isEmpty() && $x2c < $p2->x) {
+ $cross = $this->lineCrossArc($p2->x, $p2->y, $angle, $x2c, $y1c, $round, 'tr');
+ $end = new PathData($arc);
+ $end->add($cross);
+ $c_tr = new PathData('L', $x2, $y1c);
+ if($x2c < $p1->x) {
+ $cross = $this->lineCrossArc($p1->x, $p2->y, $angle, $x2c, $y1c, $round, 'tr');
+ $start = new PathData('M');
+ $start->add($cross);
+ $start->add($arc);
+ $start->add($x2c, $y1);
+ }
+ }
+ }
+ $points = [$start, $c_tl, $c_bl, $c_br, $c_tr, $end];
+ $distance = $y1 - $target[1];
+ } else {
+ // bottom
+ $p1 = new Point($cx + $hoff + $wa, $y2);
+ if($p1->x > $x2) {
+ $p1->y = $y2 - ($p1->x - $x2) * tan($angle);
+ $p1->x = $x2;
+ }
+ $p2 = new Point($cx + $hoff - $wa, $y2);
+ if($p2->x < $x1) {
+ $p2->y = $y2 + ($x1 - $p2->x) * tan($angle);
+ $p2->x = $x1;
+ }
+ $start = new PathData('M', $p1);
+ $end = new PathData('L', $p2);
+ if($p1->y < $y2c)
+ $c_br->clear();
+ if($p2->y < $y2c)
+ $c_bl->clear();
+ if($round) {
+ if(!$c_bl->isEmpty() && $p2->x < $x1c) {
+ $cross = $this->lineCrossArc($p2->x, $p2->y, $angle, $x1c, $y2c, $round, 'bl');
+ $end = new PathData($arc);
+ $end->add($cross);
+ $c_bl = new PathData('L', $x1, $y2c);
+ if($p1->x < $x1c) {
+ $cross = $this->lineCrossArc($p1->x, $p1->y, $angle, $x1c, $y2c, $round, 'bl');
+ $start = new PathData('M');
+ $start->add($cross);
+ $start->add($arc);
+ $start->add($x1c, $y2);
+ }
+ }
+ if(!$c_br->isEmpty() && $x2c < $p1->x) {
+ $cross = $this->lineCrossArc($p1->x, $p1->y, $angle, $x2c, $y2c, $round, 'br');
+ $start = new PathData('M');
+ $start->add($cross);
+ $c_br = new PathData($arc);
+ $c_br->add($x2, $y2c);
+ if($x2c < $p2->x) {
+ $cross = $this->lineCrossArc($p2->x, $p2->y, $angle, $x2c, $y2c, $round, 'br');
+ $end = new PathData('L', $x2c, $y2);
+ $end->add($arc);
+ $end->add($cross);
+ }
+ }
+ }
+ $points = [$start, $c_br, $c_tr, $c_tl, $c_bl, $end];
+ $distance = $target[1] - $y2;
+ }
+ } else {
+ // either side
+ // $voff = vertical offset from centre of side
+ // $wa = width at angle
+ $voff = $w * 0.5 * tan($angle);
+ $wa = $t_width * 0.5 / cos($angle);
+ if($angle < M_PI * 0.5 || $angle > M_PI * 1.5) {
+ // right
+ $p1 = new Point($x2, $cy + $voff - $wa);
+ if($p1->y < $y1) {
+ $p1->x = $x2 + ($y1 - $p1->y) * tan($rangle);
+ $p1->y = $y1;
+ }
+ $p2 = new Point($x2, $cy + $voff + $wa);
+ if($p2->y > $y2) {
+ $p2->x = $x2 - ($p2->y - $y2) * tan($rangle);
+ $p2->y = $y2;
+ }
+ $start = new PathData('M', $p1);
+ $end = new PathData('L', $p2);
+ if($p1->x < $x2c)
+ $c_tr->clear();
+ if($p2->x < $x2c)
+ $c_br->clear();
+ if($round) {
+ if(!$c_br->isEmpty() && $y2c < $p2->y) {
+ $cross = $this->lineCrossArc($p2->x, $p2->y, $angle, $x2c, $y2c, $round, 'br');
+ $end = new PathData($arc);
+ $end->add($cross);
+ $c_br = new PathData('L', $x2c, $y2);
+ if($y2c < $p1->y) {
+ $cross = $this->lineCrossArc($p1->x, $p1->y, $angle, $x2c, $y2c, $round, 'br');
+ $start = new PathData('M');
+ $start->add($cross);
+ $start->add($arc);
+ $start->add($x2, $y2c);
+ }
+ }
+ if(!$c_tr->isEmpty() && $p1->y < $y1c) {
+ $cross = $this->lineCrossArc($p1->x, $p1->y, $angle, $x2c, $y1c, $round, 'tr');
+ $start = new PathData('M');
+ $start->add($cross);
+ $c_tr = new PathData($arc);
+ $c_tr->add($x2c, $y1);
+ if($p2->y < $y1c) {
+ $cross = $this->lineCrossArc($p2->x, $p2->y, $angle, $x2c, $y1c, $round, 'tr');
+ $end = new PathData('L', $x2, $y1c);
+ $end->add($arc);
+ $end->add($cross);
+ }
+ }
+ }
+ $points = [$start, $c_tr, $c_tl, $c_bl, $c_br, $end];
+ $distance = $target[0] - $x2;
+ } else {
+ // left
+ $p1 = new Point($x1, $cy - $voff - $wa);
+ if($y2 < $p1->y) {
+ $p1->x = $x1 + ($y2 - $p1->y) * tan($rangle);
+ $p1->y = $y2;
+ }
+ $p2 = new Point($x1, $cy - $voff + $wa);
+ if($p2->y < $y1) {
+ $p2->x = $x1 - ($p2->y - $y1) * tan($rangle);
+ $p2->y = $y1;
+ }
+ $start = new PathData('M', $p1);
+ $end = new PathData('L', $p2);
+ if($p1->x > $x1c)
+ $c_bl->clear();
+ if($p2->x > $x1c)
+ $c_tl->clear();
+ if($round) {
+ if(!$c_bl->isEmpty() && $y2c < $p1->y) {
+ $cross = $this->lineCrossArc($p1->x, $p1->y, $angle, $x1c, $y2c, $round, 'bl');
+ $start = new PathData('M');
+ $start->add($cross);
+ $c_bl = new PathData($arc);
+ $c_bl->add($x1c, $y2);
+ if($y2c < $p2->y) {
+ $cross = $this->lineCrossArc($p2->x, $p2->y, $angle, $x1c, $y2c, $round, 'bl');
+ $end = new PathData('L', $x1, $y2c);
+ $end->add($arc);
+ $end->add($cross);
+ }
+ }
+ if(!$c_tl->isEmpty() && $p2->y < $y1c) {
+ $cross = $this->lineCrossArc($p2->x, $p2->y, $angle, $x1c, $y1c, $round, 'tl');
+ $end = new PathData($arc);
+ $end->add($cross);
+ $c_tl = new PathData('L', $x1c, $y1);
+ if($p1->y < $y1c) {
+ $cross = $this->lineCrossArc($p1->x, $p1->y, $angle, $x1c, $y1c, $round, 'tl');
+ $start = new PathData('M');
+ $start->add($cross);
+ $start->add($arc);
+ $start->add($x1, $y1c);
+ }
+ }
+ }
+ $points = [$start, $c_bl, $c_br, $c_tr, $c_tl, $end];
+ $distance = $x1 - $target[0];
+ }
+ }
+ $box_path = new PathData;
+ foreach($points as $pt)
+ $box_path->add($pt);
+ $box_path->add($this->getTailEnding($target[0], $target[1], $angle,
+ $t_width, $distance, $style));
+ $box_path->add('z');
+
+ $surround['fill'] = new Colour($this->graph, $style['fill']);
+ $surround['d'] = $box_path;
+ return 'path';
+ }
+
+ /**
+ * Line and circle label style
+ */
+ protected function circleLineLabel($x, $y, $w, $h, &$style, &$surround)
+ {
+ list($cx, $cy, $bbradius) = $this->calcRoundLabel($x, $y, $w, $h);
+ list($target, $angle, $len) = $this->getRoundTailTarget($style, $cx, $cy,
+ $bbradius);
+ if($target === null)
+ return $this->circleLabel($x, $y, $w, $h, $style, $surround);
+
+ $t_width = max(1, min($style['tail_width'], $bbradius));
+ $l_angle = asin($t_width * 0.5 / $bbradius);
+ $p1x = $cx + $bbradius * cos($angle - $l_angle);
+ $p1y = $cy + $bbradius * sin($angle - $l_angle);
+ $p2x = $cx + $bbradius * cos($angle + $l_angle);
+ $p2y = $cy + $bbradius * sin($angle + $l_angle);
+
+ $surround['fill'] = new Colour($this->graph, $style['fill']);
+ $path = new PathData('M', $p1x, $p1y, 'A', $bbradius, $bbradius, 0, 1, 0,
+ $p2x, $p2y);
+ $path->add($this->getTailEnding($target[0], $target[1], $angle, $t_width, $len, $style));
+ $path->add('z');
+ $surround['d'] = $path;
+ return 'path';
+ }
+
+ /**
+ * Converts a rectangle to a square with same centre point
+ */
+ private static function makeSquare(&$x, &$y, &$w, &$h)
+ {
+ if($w == $h)
+ return;
+ $w2 = $w / 2;
+ $h2 = $h / 2;
+ if($w > $h) {
+ $y -= ($w2 - $h2);
+ $h += ($w - $h);
+ return;
+ }
+ $x -= ($h2 - $w2);
+ $w += ($h - $w);
+ }
+
+ /**
+ * Square label style
+ */
+ protected function squareLabel($x, $y, $w, $h, &$style, &$surround)
+ {
+ $this->makeSquare($x, $y, $w, $h);
+ return $this->boxLabel($x, $y, $w, $h, $style, $surround);
+ }
+
+ /**
+ * Line and square label style
+ */
+ protected function squareLineLabel($x, $y, $w, $h, &$style, &$surround)
+ {
+ $this->makeSquare($x, $y, $w, $h);
+ return $this->boxLineLabel($x, $y, $w, $h, $style, $surround);
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/DateTimeFormatter.php b/classes/vendor/81x/goat1000/svggraph/DateTimeFormatter.php
new file mode 100644
index 0000000..01287ba
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/DateTimeFormatter.php
@@ -0,0 +1,147 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * Class for formatting date/time values
+ */
+class DateTimeFormatter {
+
+ protected $timezone = null;
+ protected $localize = false;
+ protected $idf = null;
+
+ public function __construct()
+ {
+ $this->timezone = new \DateTimeZone(date_default_timezone_get());
+
+ // see if output needs localization
+ if(extension_loaded('intl')) {
+ $locale = setlocale(LC_TIME, 0);
+ if($locale && $locale !== 'C' && $locale !== 'POSIX' &&
+ strpos($locale, 'en_') === false) {
+ $this->localize = true;
+ $this->idf = new \IntlDateFormatter($locale,
+ \IntlDateFormatter::FULL, \IntlDateFormatter::FULL);
+ }
+ }
+ }
+
+ /**
+ * Returns the formatted, localized date/time
+ */
+ public function format($dt, $fmt, $strip_offset = false)
+ {
+ $datetime = clone $dt;
+ $datetime->setTimezone($this->timezone);
+
+ if($strip_offset) {
+ $offset = $this->timezone->getOffset($datetime);
+ if($offset < 0)
+ $datetime->modify($offset . ' second');
+ else
+ $datetime->modify('+' . $offset . ' second');
+ }
+
+ if(!$this->localize)
+ return $datetime->format($fmt);
+
+ // DateTime class doesn't do localization, so these fields are passed to
+ // IntlDateFormatter instead
+ $map = [
+ 'D' => 'E',
+ 'l' => 'EEEE',
+ 'M' => 'MMM',
+ 'F' => 'MMMM',
+ ];
+
+ $result = '';
+ $unixtime = $datetime->format('U');
+ for($i = 0; $i < strlen($fmt); ++$i) {
+ $char = $fmt[$i];
+ if(isset($map[$char])) {
+ $this->idf->setPattern($map[$char]);
+ $result .= $this->idf->format($unixtime);
+ } else {
+ $result .= $datetime->format($fmt[$i]);
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Returns the list of day names
+ */
+ public function getLongDays()
+ {
+ return $this->getDateStrings('l', 'd');
+ }
+
+ /**
+ * Returns the list of abbreviated day names
+ */
+ public function getShortDays()
+ {
+ return $this->getDateStrings('D', 'd');
+ }
+
+ /**
+ * Returns the list of month names
+ */
+ public function getLongMonths()
+ {
+ return $this->getDateStrings('F', 'm');
+ }
+
+ /**
+ * Returns the list of abbreviated month names
+ */
+ public function getShortMonths()
+ {
+ return $this->getDateStrings('M', 'm');
+ }
+
+ /**
+ * Returns a list of day or month strings using IntlDateFormatter to localize
+ */
+ private function getDateStrings($fmt, $inc)
+ {
+ // 1978 started on a Sunday
+ $dt = new \DateTime('1978-01-01T12:00:00Z');
+ if($inc == 'm') {
+ $count = 12;
+ $offset = 'month';
+ } else {
+ $count = 7;
+ $offset = 'day';
+ }
+
+ $strings = [];
+ for($i = 0; $i < $count; ++$i) {
+ if($i)
+ $dt->modify('+1 ' . $offset);
+ $strings[] = $this->format($dt, $fmt);
+ }
+ return $strings;
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/Defs.php b/classes/vendor/81x/goat1000/svggraph/Defs.php
new file mode 100644
index 0000000..a1dad3b
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/Defs.php
@@ -0,0 +1,176 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * A class for the element
+ */
+class Defs {
+
+ private $graph;
+ private $defs = [];
+ private $gradients = null;
+ private $patterns = null;
+ private $symbols = null;
+ private $filters = null;
+ private $elements = [];
+
+ public function __construct(&$graph)
+ {
+ $this->graph =& $graph;
+ }
+
+ /**
+ * Add a string to the defs block
+ */
+ public function add($def)
+ {
+ $this->defs[] = $def;
+ }
+
+ /**
+ * Adds an element to the defs, returning its ID, or the ID
+ * of an existing def with same content
+ */
+ public function addElement($element, $attrs, $content = '')
+ {
+ $ehash = hash('md5', $element . ':' . serialize($attrs) . ':' . $content);
+ if(isset($this->elements[$ehash]))
+ return $this->elements[$ehash];
+
+ $attrs['id'] = $this->graph->newID();
+ $this->elements[$ehash] = $attrs['id'];
+ $this->add($this->graph->element($element, $attrs, null, $content));
+ return $attrs['id'];
+ }
+
+ /**
+ * Return the defs block, or an empty string if none
+ */
+ public function get()
+ {
+ // insert gradients, patterns, symbols
+ if($this->gradients !== null)
+ $this->gradients->makeGradients($this);
+ if($this->patterns !== null)
+ $this->patterns->makePatterns($this);
+ if($this->symbols !== null)
+ $this->defs[] = $this->symbols->definitions();
+ if($this->filters !== null)
+ $this->filters->makeFilters($this);
+
+ if(count($this->defs) == 0)
+ return '';
+
+ return $this->graph->element('defs', null, null, implode('', $this->defs));
+ }
+
+ /**
+ * Adds a gradient to the list, returning the element ID for use in url
+ */
+ public function addGradient($colours, $key = null, $radial = false)
+ {
+ if($this->gradients === null)
+ $this->gradients = new GradientList($this->graph);
+ return $this->gradients->addGradient($colours, $key, $radial);
+ }
+
+ /**
+ * Returns the colour at a point in the selected gradient
+ */
+ public function getGradientColour($key, $position)
+ {
+ if($this->gradients === null)
+ return 'none';
+
+ return $this->gradients->getColour($key, $position);
+ }
+
+ /**
+ * Adds a pattern, returning the element ID
+ */
+ public function addPattern($pattern)
+ {
+ if($this->patterns === null)
+ $this->patterns = new PatternList($this->graph);
+ return $this->patterns->add($pattern);
+ }
+
+ /**
+ * Defines a symbol
+ */
+ public function defineSymbol($content)
+ {
+ if($this->symbols === null)
+ $this->symbols = new Symbols($this->graph);
+ return $this->symbols->define($content);
+ }
+
+ /**
+ * Uses a symbol
+ */
+ public function useSymbol($id, $attr, $style = null)
+ {
+ // this should not happen - Symbols class will throw anyway
+ if($this->symbols === null)
+ $this->symbols = new Symbols($this->graph);
+ return $this->symbols->useSymbol($id, $attr, $style);
+ }
+
+ /**
+ * Returns the use count for a symbol
+ */
+ public function symbolUseCount($id)
+ {
+ if($this->symbols === null)
+ return 0;
+ return $this->symbols->useCount($id);
+ }
+
+ /**
+ * Adds a filter
+ */
+ public function addFilter($type, $params = null)
+ {
+ if($this->filters === null)
+ $this->filters = new FilterList($this->graph);
+ return $this->filters->add($type, $params);
+ }
+
+ /**
+ * Returns id of shadow, if enabled
+ */
+ public function getShadow()
+ {
+ if(!$this->graph->getOption('show_shadow'))
+ return null;
+
+ $filter_id = $this->addFilter('shadow', [
+ 'opacity' => $this->graph->getOption('shadow_opacity'),
+ 'offset_x' => $this->graph->getOption('shadow_offset_x'),
+ 'offset_y' => $this->graph->getOption('shadow_offset_y'),
+ 'blur' => $this->graph->getOption('shadow_blur'),
+ ]);
+ return $filter_id;
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/DisplayAxis.php b/classes/vendor/81x/goat1000/svggraph/DisplayAxis.php
new file mode 100644
index 0000000..b0d1952
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/DisplayAxis.php
@@ -0,0 +1,1097 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * Converts an abstract axis into SVG markup
+ */
+class DisplayAxis {
+
+ protected $graph;
+ protected $axis;
+ protected $axis_no;
+ protected $orientation;
+ protected $type;
+ protected $main;
+ protected $styles;
+ protected $show_axis;
+ protected $show_divisions;
+ protected $show_subdivisions;
+ protected $show_text;
+ protected $show_label;
+ protected $label = '';
+ protected $block_label;
+ protected $boxed_text;
+ protected $minimum_subdivision;
+ protected $minimum_units;
+ protected $subdivisions_fixed;
+ protected $offset = [0, 0];
+
+ /**
+ * $orientation = 'h' or 'v'
+ * $type = 'x' or 'y'
+ * $main = TRUE for the main axis
+ * $label_centre = TRUE for graphs with labels between divisions
+ */
+ public function __construct(&$graph, &$axis, $axis_no, $orientation, $type,
+ $main, $label_centre)
+ {
+ $this->axis_no = $axis_no;
+ $this->orientation = $orientation;
+ $this->type = $type;
+ $this->main = $main;
+ $this->block_label = ($type == 'x' && ($label_centre ||
+ $graph->getOption('force_block_label_x')));
+ $this->boxed_text = false;
+ $styles = [];
+
+ // set up options, styles
+ $o = $orientation;
+ $this->show_axis = false;
+ $this->show_text = false;
+ if($graph->getOption('show_axes')) {
+ $this->show_axis = $graph->getOption(['show_axis_' . $o, $axis_no]);
+ $this->show_text = $graph->getOption('show_axis_text_' . $o);
+ }
+
+ // offset caused by padding between axis and grid
+ if($o == 'v') {
+ switch($axis_no) {
+ case 0: $this->offset[0] = -$graph->getOption('axis_pad_left');
+ break;
+ case 1: $this->offset[0] = $graph->getOption('axis_pad_right');
+ break;
+ }
+ } else {
+ switch($axis_no) {
+ case 0: $this->offset[1] = $graph->getOption('axis_pad_bottom');
+ break;
+ case 1: $this->offset[1] = -$graph->getOption('axis_pad_top');
+ break;
+ }
+ }
+
+ // lambda to make retrieving options simpler
+ $get_axis_option = function($option) use ($graph, $o, $axis_no) {
+ return $graph->getOption([$option . '_' . $o, $axis_no], $option);
+ };
+
+ // gridgraph moves label_[xy] into label_[hv]
+ $o_labels = $graph->getOption('label_' . $o);
+ if(is_array($o_labels)) {
+ // use array entry if one exists for this axis
+ if(isset($o_labels[$axis_no]))
+ $this->label = $o_labels[$axis_no];
+ } elseif($axis_no == 0 && !empty($o_labels)) {
+ // not an array, so only valid for axis 0
+ $this->label = $o_labels;
+ }
+ $this->show_label = ($this->label != '');
+
+ // axis and text both need colour
+ $styles['colour'] = new Colour($graph, $get_axis_option('axis_colour'));
+ if($this->show_axis) {
+ $styles['overlap'] = $graph->getOption('axis_overlap');
+ $styles['stroke_width'] = $get_axis_option('axis_stroke_width');
+ if($o == 'v') {
+ $styles['extend'] = [
+ $graph->getOption('axis_extend_top', 'axis_pad_top'),
+ $graph->getOption('axis_extend_bottom', 'axis_pad_bottom'),
+ ];
+ } else {
+ $styles['extend'] = [
+ $graph->getOption('axis_extend_left', 'axis_pad_left'),
+ $graph->getOption('axis_extend_right', 'axis_pad_right'),
+ ];
+ }
+
+ if($graph->getOption('show_divisions')) {
+ $this->show_divisions = true;
+ $styles['d_style'] = $get_axis_option('division_style');
+ $styles['d_size'] = $get_axis_option('division_size');
+ $styles['d_colour'] = new Colour($graph, $graph->getOption(
+ ['division_colour_' . $o, $axis_no], 'division_colour',
+ ['@', $styles['colour']]));
+
+ if($graph->getOption('show_subdivisions')) {
+ $this->show_subdivisions = true;
+ $styles['s_style'] = $get_axis_option('subdivision_style');
+ $styles['s_size'] = $get_axis_option('subdivision_size');
+ $styles['s_colour'] = new Colour($graph, $graph->getOption(
+ ['subdivision_colour_' . $o, $axis_no], 'subdivision_colour',
+ ['division_colour_' . $o, $axis_no], 'division_colour',
+ ['@', $styles['colour']]));
+ $this->minimum_subdivision = $graph->getOption('minimum_subdivision');
+ $this->minimum_units = ($type == 'x' ? 1 :
+ $graph->getOption(['minimum_units_y', $axis_no]));
+ $this->subdivisions_fixed = $graph->getOption(
+ ['subdivision_' . $o, $axis_no]);
+ }
+ }
+ }
+
+ if($this->show_text) {
+ $styles['t_angle'] = $graph->getOption(
+ ['axis_text_angle_' . $o, $axis_no], 0);
+ if($graph->getOption('limit_text_angle')) {
+ $angle = $styles['t_angle'] % 360;
+ if($angle > 180)
+ $angle = -360 + $angle;
+ if($angle < -180)
+ $angle = 360 + $angle;
+ $angle = min(90, max(-90, $angle));
+ $styles['t_angle'] = $angle;
+ }
+ $styles['t_position'] = $get_axis_option('axis_text_position');
+ $styles['t_location'] = $get_axis_option('axis_text_location');
+ $styles['t_font'] = $get_axis_option('axis_font');
+ $styles['t_font_size'] = Number::units($get_axis_option('axis_font_size'));
+ $styles['t_font_adjust'] = $get_axis_option('axis_font_adjust');
+ $styles['t_font_weight'] = $get_axis_option('axis_font_weight');
+ $styles['t_space'] = $get_axis_option('axis_text_space');
+ $styles['t_colour'] = new Colour($graph, $graph->getOption(
+ ['axis_text_colour_' . $o, $axis_no], 'axis_text_colour',
+ ['@', $styles['colour']]));
+ $styles['t_line_spacing'] = Number::units($get_axis_option('axis_text_line_spacing'));
+ if($styles['t_line_spacing'] === null || $styles['t_line_spacing'] < 1)
+ $styles['t_line_spacing'] = $styles['t_font_size'];
+ $styles['t_back_colour'] = null;
+ $styles['t_align'] = $get_axis_option('axis_text_align');
+
+ // fill in background colour array, if required
+ $back_colour = $get_axis_option('axis_text_back_colour');
+ if(!empty($back_colour)) {
+ $styles['t_back_colour'] = [
+ 'stroke-width' => '3px',
+ 'stroke' => new Colour($graph, $back_colour),
+ 'stroke-linejoin' => 'round',
+ ];
+ }
+
+ // text is boxed only if it is outside and block labelling
+ if($this->block_label && $this->show_divisions &&
+ ($styles['d_style'] == 'box' || $styles['d_style'] == 'extend') &&
+ $styles['t_position'] != 'inside') {
+ $this->boxed_text = true;
+
+ // text must be attached to axis, not grid
+ $styles['t_location'] = 'axis';
+ }
+ }
+
+ if($this->show_label) {
+ $styles['l_font'] = $graph->getOption(
+ ['label_font_' . $o, $axis_no], 'label_font',
+ ['axis_font_' . $o, $axis_no], 'axis_font');
+ $styles['l_font_size'] = Number::units($graph->getOption(
+ ['label_font_size_' . $o, $axis_no], 'label_font_size',
+ ['axis_font_size_' . $o, $axis_no], 'axis_font_size'));
+ $styles['l_font_weight'] = $graph->getOption(
+ ['label_font_weight_' . $o, $axis_no], 'label_font_weight');
+ $styles['l_colour'] = new Colour($graph, $graph->getOption(
+ ['label_colour_' . $o, $axis_no], 'label_colour',
+ ['axis_text_colour_' . $o, $axis_no], 'axis_text_colour',
+ ['@', $styles['colour']]));
+ $styles['l_space'] = $graph->getOption('label_space');
+ $styles['l_pos'] = $get_axis_option('axis_label_position');
+ $styles['l_line_spacing'] = Number::units($get_axis_option('label_line_spacing'));
+ if($styles['l_line_spacing'] === null || $styles['l_line_spacing'] < 1)
+ $styles['l_line_spacing'] = $styles['l_font_size'];
+ }
+
+ $this->styles = $styles;
+ $this->axis =& $axis;
+ $this->graph =& $graph;
+ }
+
+ /**
+ * Returns the array of style information for the axis
+ */
+ public function getStyling()
+ {
+ return $this->styles;
+ }
+
+ /**
+ * Returns the extents of the axis, relative to where it will be drawn from
+ * returns BoundingBox
+ */
+ public function measure($with_label = true)
+ {
+ $bbox = new BoundingBox(0, 0, 0, 0);
+ if($this->show_axis) {
+ $dbox = $this->getDivisionsBBox(0);
+ if($this->orientation == 'h')
+ $dbox->flipY();
+ $bbox->growBox($dbox);
+ }
+
+ if($this->show_text) {
+ $tbox = $this->getTextBBox(0);
+ $bbox->growBox($tbox);
+ }
+
+ if($with_label && $this->show_label) {
+ $lpos = $this->getLabelPosition();
+ $bbox->grow($lpos['x'], $lpos['y'], $lpos['x'] + $lpos['width'],
+ $lpos['y'] + $lpos['height']);
+ }
+ $bbox = $this->addOffset($bbox);
+
+ return $bbox;
+ }
+
+ /**
+ * Adds the padding offset to the bounding box
+ */
+ protected function addOffset(BoundingBox $bbox)
+ {
+ if($this->axis_no > 0) {
+ $bbox->x2 += $this->offset[0];
+ $bbox->y1 += $this->offset[1];
+ } else {
+ $bbox->x1 += $this->offset[0];
+ $bbox->y2 += $this->offset[1];
+ }
+ return $bbox;
+ }
+
+ /**
+ * Measures the space taken up by axis and divisions
+ * returns BoundingBox
+ */
+ protected function getDivisionsBBox($level)
+ {
+ $bbox = new BoundingBox(0, 0, 0, 0);
+ if(!$this->show_axis)
+ return $bbox;
+
+ // orientation more important than type
+ $x = 'x';
+ $y = 'y';
+ if($this->orientation == 'h') {
+ $x = 'y';
+ $y = 'x';
+ }
+ $min = $max = ['x' => 0, 'y' => 0];
+
+ $length = $this->axis->getLength();
+ $d_info = $s_info = ['pos' => 0, 'sz' => 0];
+ if($this->show_divisions) {
+ $di = $this->getDivisionPathInfo(false, 100, 100, $level);
+ if($di !== null)
+ $d_info = $di;
+ if($this->show_subdivisions) {
+ $si = $this->getDivisionPathInfo(true, 100, 100, $level);
+ if($si !== null)
+ $s_info = $si;
+ }
+ }
+
+ $points = $this->axis->getGridPoints(0);
+ $p1 = end($points);
+ if($p1->position < 0) {
+ $min[$y] = $p1->position;
+ $max[$y] = 0;
+ } else {
+ $min[$y] = 0;
+ $max[$y] = $p1->position;
+ }
+
+ $min[$x] = min($d_info['pos'], $s_info['pos']);
+ $max[$x] = max($d_info['pos'] + $d_info['sz'], $s_info['pos'] + $s_info['sz']);
+
+ $bbox->grow($min['x'], $min['y'], $max['x'], $max['y']);
+ return $bbox;
+ }
+
+ /**
+ * Returns the bounding box of the text
+ */
+ protected function getTextBBox($level)
+ {
+ $bbox = new BoundingBox(0, 0, 0, 0);
+ list($x_off, $y_off, $opp) = $this->getTextOffset(0, 0, 0, 0, 0, 0, $level);
+
+ $t_offset = ($this->orientation == 'h' ? $x_off : $y_off);
+ if($this->axis->reversed())
+ $t_offset = -$t_offset;
+
+ $length = $this->axis->getLength();
+ $points = $this->axis->getGridPoints(0);
+ $positions = $this->getTextPositions(0, 0, $x_off, $y_off, $points, null);
+ $count = count($positions);
+ for($p = 0; $p < $count; ++$p) {
+
+ if(!$this->pointTextVisible($points[$p], $length, $t_offset))
+ continue;
+
+ $pos = $positions[$p];
+ $lbl = $this->measureText($pos['x'], $pos['y'], $points[$p], $opp, $level);
+ $bbox->grow($lbl['x'], $lbl['y'], $lbl['x'] + $lbl['width'],
+ $lbl['y'] + $lbl['height']);
+ }
+ return $bbox;
+ }
+
+ /**
+ * Returns the overlap between axis text labels
+ */
+ public function getTextOverlap()
+ {
+ // start with obviously good overlap
+ $overlap = -1000;
+ $prev_x = -10;
+ $level = 0;
+
+ // no overlap if there is no text
+ if(!$this->show_text)
+ return null;
+
+ list($x_off, $y_off, $opp) = $this->getTextOffset(0, 0, 0, 0, 0, 0, $level);
+ $t_offset = ($this->orientation == 'h' ? $x_off : $y_off);
+ if($this->axis->reversed())
+ $t_offset = -$t_offset;
+
+ $length = $this->axis->getLength();
+ $points = $this->axis->getGridPoints(0);
+ $positions = $this->getTextPositions(0, 0, $x_off, $y_off, $points, null);
+ $count = count($positions);
+
+ for($p = 0; $p < $count; ++$p) {
+ if(!$this->pointTextVisible($points[$p], $length, $t_offset))
+ continue;
+
+ $pos = $positions[$p];
+ $lbl = $this->measureText($pos['x'], $pos['y'], $points[$p], $opp, $level);
+ $o = $prev_x - $lbl['x'];
+
+ // bail out now if the overlap is positive
+ if($o > 0)
+ return $o;
+ if($o > $overlap)
+ $overlap = $o;
+ $prev_x = $lbl['x'] + $lbl['width'];
+ }
+ return $overlap;
+ }
+
+ /**
+ * Returns true if the text exists and its location is within the axis
+ */
+ protected function pointTextVisible($point, $axis_len, $offset)
+ {
+ if($point->blank())
+ return false;
+
+ // amount of space to allow for rounding errors
+ $leeway = 0.5;
+ $position = abs($point->position) + $offset;
+ return $position >= -$leeway && $position <= $axis_len + $leeway;
+ }
+
+ /**
+ * Returns the positions (actually offsets) of all the text blocks
+ */
+ protected function getTextPositions($x, $y, $xoff, $yoff, $points, $anchor)
+ {
+ $positions = [];
+ // vertical axis a bit simpler
+ if($this->orientation == 'v') {
+ foreach($points as $k => $p) {
+ if($this->boxed_text && isset($points[$k + 1])) {
+ $pnext = $points[$k + 1];
+ $yoff = ($pnext->position - $p->position) / 2;
+ }
+ $positions[] = ['x' => $x + $xoff, 'y' => $y + $yoff];
+ }
+ return $positions;
+ }
+
+ foreach($points as $k => $p) {
+ if($anchor == 'start') {
+ $xoff = $this->styles['t_space'];
+ } elseif($anchor == 'end') {
+ $pnext = $points[$k + 1];
+ $xoff = $pnext->position - $p->position - $this->styles['t_space'];
+ } elseif($this->boxed_text && isset($points[$k + 1])) {
+ $pnext = $points[$k + 1];
+ $xoff = ($pnext->position - $p->position) / 2;
+ }
+ $positions[] = ['x' => $x + $xoff, 'y' => $y + $yoff];
+ }
+ return $positions;
+ }
+
+ /**
+ * Draws the axis at ($x,$y)
+ * $gx, $gy, $g_width and $g_height are the grid dimensions
+ */
+ public function draw($x, $y, $gx, $gy, $g_width, $g_height)
+ {
+ $content = '';
+ $x += $this->offset[0];
+ $y += $this->offset[1];
+ $gx += $this->offset[0];
+ $gy += $this->offset[1];
+ if($this->show_axis) {
+ if($this->show_divisions) {
+ $content .= $this->drawDivisions($x, $y, $g_width, $g_height);
+ if($this->show_subdivisions) {
+ $content .= $this->drawSubDivisions($x, $y, $g_width, $g_height);
+ }
+ }
+
+ $length = $this->axis->getLength();
+ $content .= $this->drawAxisLine($x, $y, $length);
+ }
+
+ if($this->show_text || $this->show_label)
+ $content .= $this->drawText($x, $y, $gx, $gy, $g_width, $g_height);
+
+ return $content;
+ }
+
+ /**
+ * Draws the axis line
+ */
+ public function drawAxisLine($x, $y, $len)
+ {
+ $overlap = $this->styles['overlap'];
+ $length = $len + $overlap * 2;
+ $line = $this->orientation; // 'h' and 'v' are SVG path lines
+ $reversed = $this->axis->reversed();
+ if($this->orientation == 'h') {
+ $x = $reversed ? $x - $overlap - $len : $x - $overlap;
+ $x -= $this->styles['extend'][0];
+ $length += $this->styles['extend'][0] + $this->styles['extend'][1];
+ } else {
+ $y = $reversed ? $y - $overlap - $len : $y - $overlap;
+ $y -= $this->styles['extend'][0];
+ $length += $this->styles['extend'][0] + $this->styles['extend'][1];
+ }
+
+ $colour = $this->styles['colour'];
+ if($colour->isGradient()) {
+ // gradients don't work on stroked horizontal or vertical lines
+ $sw = $this->styles['stroke_width'];
+ $attr = [
+ 'fill' => $colour,
+ 'x' => $x,
+ 'y' => $y,
+ ];
+ if($this->orientation == 'h') {
+ $attr['y'] -= $sw / 2;
+ $attr['width'] = $length;
+ $attr['height'] = $sw;
+ } else {
+ $attr['x'] -= $sw / 2;
+ $attr['width'] = $sw;
+ $attr['height'] = $length;
+ }
+ return $this->graph->element('rect', $attr);
+ }
+
+ $attr = [
+ 'stroke' => $colour,
+ 'd' => new PathData('M', $x, $y, $line, $length),
+ ];
+
+ if($this->styles['stroke_width'] != 1)
+ $attr['stroke-width'] = $this->styles['stroke_width'];
+ return $this->graph->element('path', $attr);
+ }
+
+ /**
+ * Draws the axis divisions
+ */
+ public function drawDivisions($x, $y, $g_width, $g_height)
+ {
+ $path_info = $this->getDivisionPathInfo(false, $g_width, $g_height, 0);
+ if($path_info === null)
+ return '';
+
+ $points = $this->axis->getGridPoints(0);
+ $d = $this->getDivisionPath($x, $y, $points, $path_info, 0);
+
+ $attr = [
+ 'd' => $d,
+ 'stroke' => $this->styles['d_colour'],
+ 'fill' => 'none',
+ ];
+ return $this->graph->element('path', $attr);
+ }
+
+ /**
+ * Draws the axis subdivisions
+ */
+ public function drawSubDivisions($x, $y, $g_width, $g_height)
+ {
+ $path_info = $this->getDivisionPathInfo(true, $g_width, $g_height, 0);
+ if($path_info === null)
+ return '';
+
+ $points = $this->axis->getGridSubdivisions($this->minimum_subdivision,
+ $this->minimum_units, 0, $this->subdivisions_fixed);
+ $d = $this->getDivisionPath($x, $y, $points, $path_info, 0);
+ $attr = [
+ 'd' => $d,
+ 'stroke' => $this->styles['s_colour'],
+ 'fill' => 'none',
+ ];
+ return $this->graph->element('path', $attr);
+ }
+
+ /**
+ * Draws the axis text labels
+ */
+ public function drawText($x, $y, $gx, $gy, $g_width, $g_height)
+ {
+ $labels = '';
+ if($this->show_text) {
+ list($x_offset, $y_offset, $opposite) = $this->getTextOffset($x, $y,
+ $gx, $gy, $g_width, $g_height, 0);
+
+ $t_offset = ($this->orientation == 'h' ? $x_offset : $y_offset);
+ if($this->axis->reversed())
+ $t_offset = -$t_offset;
+
+ $length = $this->axis->getLength();
+ $points = $this->axis->getGridPoints(0);
+ $anchor = null;
+ $space = $this->styles['t_space'];
+ if($this->styles['t_align']) {
+ $bbox = $this->measure(false);
+ switch($this->styles['t_align']) {
+ case 'left':
+ if($this->orientation == 'v') {
+ if(!$opposite) {
+ $anchor = 'start';
+ $x_offset = $bbox->x1 + $space;
+ $x_offset -= $this->offset[0];
+ }
+ } else {
+ $anchor = 'start';
+ $x_offset = $space;
+ }
+ break;
+ case 'right':
+ if($this->orientation == 'v') {
+ if($opposite) {
+ $anchor = 'end';
+ $x_offset = $bbox->x2 - $space;
+ }
+ } else {
+ $anchor = 'end';
+ $x_offset = -$space;
+ }
+ break;
+ case 'centre':
+ if($this->orientation == 'v') {
+ $x_offset = ($x_offset + ($opposite ? $bbox->x2 : $bbox->x1)) / 2;
+ $anchor = 'middle';
+ }
+ }
+ }
+
+ $positions = $this->getTextPositions($x, $y, $x_offset, $y_offset, $points, $anchor);
+ $count = count($positions);
+ for($p = 0; $p < $count; ++$p) {
+
+ if(!$this->pointTextVisible($points[$p], $length, $t_offset))
+ continue;
+
+ $pos = $positions[$p];
+ $labels .= $this->getText($pos['x'], $pos['y'], $points[$p], $opposite, 0, $anchor);
+ }
+ if($labels != '') {
+ $group = [
+ 'font-family' => $this->styles['t_font'],
+ 'font-size' => $this->styles['t_font_size'],
+ 'fill' => $this->styles['t_colour'],
+ ];
+ $weight = $this->styles['t_font_weight'];
+ if($weight != 'normal' && $weight !== null)
+ $group['font-weight'] = $weight;
+
+ $labels = $this->graph->element('g', $group, null, $labels);
+ }
+ }
+ if($this->show_label)
+ $labels .= $this->getLabel($x, $y, $gx, $gy, $g_width, $g_height);
+
+ return $labels;
+ }
+
+ /**
+ * Returns the label
+ */
+ protected function getLabel($x, $y, $gx, $gy, $g_width, $g_height)
+ {
+ $pos = $this->getLabelPosition();
+ $offset = $this->getLabelOffset($x, $y, $gx, $gy, $g_width, $g_height);
+ $tx = $offset['x'] + $pos['tx'];
+ $ty = $offset['y'] + $pos['ty'];
+ $label = [
+ 'text-anchor' => 'middle',
+ 'font-family' => $this->styles['l_font'],
+ 'font-size' => $this->styles['l_font_size'],
+ 'fill' => $this->styles['l_colour'],
+ 'x' => $tx,
+ 'y' => $ty,
+ ];
+ if(!empty($this->styles['l_font_weight']) &&
+ $this->styles['l_font_weight'] != 'normal')
+ $label['font-weight'] = $this->styles['l_font_weight'];
+ if($pos['angle']) {
+ $xform = new Transform;
+ $xform->rotate($pos['angle'], $tx, $ty);
+ $label['transform'] = $xform;
+ }
+
+ $svg_text = new Text($this->graph, $this->styles['l_font']);
+ return $svg_text->text($this->label, $this->styles['l_line_spacing'], $label);
+ }
+
+ /**
+ * Returns the corrected offset for the label
+ */
+ protected function getLabelOffset($x, $y, $gx, $gy, $g_width, $g_height)
+ {
+ if($this->orientation == 'h') {
+
+ // label at bottom of grid?
+ if($this->axis_no == 0)
+ $y = $gy + $g_height;
+
+ } else {
+
+ // leftmost axis label must be outside grid
+ if($this->axis_no == 0)
+ $x = $gx;
+ }
+ return ['x' => $x, 'y' => $y];
+ }
+
+ /**
+ * Returns the dimensions of the label
+ * x, y, width, height = position and size
+ * tx, tx = text anchor point
+ * angle = text angle
+ */
+ protected function getLabelPosition()
+ {
+ $bbox = $this->measure(false);
+ $font_size = $this->styles['l_font_size'];
+ $line_spacing = $this->styles['l_line_spacing'];
+ $svg_text = new Text($this->graph, $this->styles['l_font']);
+ $tsize = $svg_text->measure($this->label, $font_size, 0, $line_spacing);
+ $baseline = $svg_text->baseline($font_size);
+ $a_length = $this->axis->getLength();
+ $space = $this->styles['l_space'];
+ $align = $this->styles['l_pos'];
+
+ if($this->orientation == 'h') {
+ $width = $tsize[0];
+ $height = $tsize[1];
+ if($this->axis_no > 0) {
+ $y = $bbox->y1 - $space - $height;
+ } else {
+ $y = $bbox->y2 + $space;
+ }
+ $y -= $this->offset[1]; // handle padding offset
+ if(is_numeric($align)) {
+ $tx = $a_length * $align;
+ } else {
+ switch($align) {
+ case 'left' :
+ $tx = $width / 2;
+ break;
+ case 'right':
+ $tx = $a_length - ($width / 2);
+ break;
+ default:
+ $tx = $a_length / 2;
+ break;
+ }
+ }
+ $ty = $y + $baseline;
+ $x = $tx - $width / 2;
+
+ $height += $space;
+ $angle = 0;
+ } else {
+ $width = $tsize[1];
+ $height = $tsize[0];
+ $reversed = $this->axis->reversed();
+ if($this->axis_no > 0) {
+ $x = $bbox->x2 + $space;
+ $x -= $this->offset[0]; // handle padding offset
+ $tx = $x + $width - $baseline;
+ } else {
+ $x = $bbox->x1 - $space - $width;
+ $x -= $this->offset[0]; // handle padding offset
+ $tx = $x + $baseline;
+ $x -= $space;
+ }
+ if(is_numeric($align)) {
+ $ty = $reversed ? -$a_length * $align : $a_length * $align;
+ } else {
+ switch($align) {
+ case 'top' :
+ $ty = $reversed ? -$a_length + $height / 2 : $height / 2;
+ break;
+ case 'bottom':
+ $ty = $reversed ? -$height / 2 : $a_length - $height / 2;
+ break;
+ default:
+ $ty = $reversed ? -$a_length / 2 : $a_length / 2;
+ break;
+ }
+ }
+ $y = $ty - $height / 2;
+ $width += $space;
+ $angle = $this->axis_no > 0 ? 90 : 270;
+ }
+
+ return compact('x', 'y', 'width', 'height', 'tx', 'ty', 'angle');
+ }
+
+ /**
+ * Returns the distance from the axis to draw the text
+ */
+ protected function getTextOffset($ax, $ay, $gx, $gy, $g_width, $g_height,
+ $level)
+ {
+ $d1 = $d2 = 0;
+ if($this->show_divisions && $level == 0) {
+ if(!$this->boxed_text) {
+ $d_info = $this->getDivisionPathInfo(false, 100, 100, 0);
+ if($d_info !== null) {
+ $d1 = $d_info['pos'];
+ $d2 = $d1 + $d_info['sz'];
+ }
+ }
+ if($this->show_subdivisions) {
+ $s_info = $this->getDivisionPathInfo(true, 100, 100, 0);
+ if($s_info !== null) {
+ $s1 = $s_info['pos'];
+ $s2 = $s1 + $s_info['sz'];
+ $d1 = min($d1, $s1);
+ $d2 = max($d2, $s2);
+ }
+ }
+ }
+ $space = $this->styles['t_space'];
+ $d1 -= $space;
+ $d2 += $space;
+ $opposite = ($this->styles['t_position'] == 'inside');
+ $reversed = $this->axis->reversed();
+ $block_offset = $this->axis->unit() * ($reversed ? -0.5 : 0.5);
+ $grid = ($this->styles['t_location'] == 'grid');
+ if($this->axis_no > 0)
+ $opposite = !$opposite;
+ if($this->orientation == 'h') {
+ $y = ($opposite ? -$d2 : -$d1);
+ $x = ($this->block_label ? $block_offset : 0);
+ if($grid) {
+ $y += $gy + $g_height - $ay;
+ if($this->axis_no == 1)
+ $y -= $g_height;
+ }
+ } else {
+ $x = ($opposite ? $d2 : $d1);
+ $y = ($this->block_label ? $block_offset : 0);
+ if($grid && $this->axis_no < 2) {
+ $x += $gx - $ax;
+ if($this->axis_no == 1)
+ $x += $g_width;
+ }
+ }
+ return [$x, $y, $opposite];
+ }
+
+ /**
+ * Returns the bounding box for a single axis label
+ */
+ protected function measureText($x, $y, &$point, $opposite, $level)
+ {
+ list($svg_text, $font_size, $attr, $anchor, $rcx, $rcy, $angle,
+ $line_spacing) = $this->getTextInfo($x, $y, $point, $opposite, $level);
+
+ // find (rotated) size and position now
+ list($x, $y, $w, $h) = $svg_text->measurePosition($point->getText($level),
+ $font_size, $line_spacing, $attr['x'], $attr['y'], $anchor, $angle,
+ $rcx, $rcy);
+
+ // per-item text indent last
+ if($point->item && $point->axis_text_indent) {
+ if($opposite)
+ $w += $point->axis_text_indent;
+ else
+ $x -= $point->axis_text_indent;
+ }
+ return ['x' => $x, 'y' => $y, 'width' => $w, 'height' => $h];
+ }
+
+ /**
+ * Returns the SVG fragment for a single axis label
+ */
+ protected function getText($x, $y, &$point, $opposite, $level, $anchor)
+ {
+ // skip 0 on axis when it would sit on top of other axis
+ if(!$this->block_label && $point->value == 0) {
+ if($this->axis_no == 0 && $opposite)
+ return '';
+ if($this->axis_no == 1 && !$opposite)
+ return '';
+ }
+
+ // see if the text needs shifting
+ if($point->item && $point->axis_text_indent) {
+ switch($this->styles['t_align']) {
+ case 'left' :
+ $x += $point->axis_text_indent;
+ break;
+ case 'centre':
+ // centred things can't be indented
+ break;
+ case 'right':
+ default:
+ $x -= $point->axis_text_indent;
+ break;
+ }
+ }
+
+ $string = $point->getText($level);
+ $text_out = '';
+ $text_info = $this->getTextInfo($x, $y, $point, $opposite, $level);
+ $svg_text = $text_info[0];
+ $attr = $text_info[2];
+ $line_spacing = $text_info[7];
+ $back_colour = $this->styles['t_back_colour'];
+
+ // $anchor overrides the one based on axis text location
+ if($anchor)
+ $attr['text-anchor'] = $anchor;
+
+ // structured data attributes?
+ if($point->item) {
+ if($point->axis_text_font)
+ $attr['font-family'] = $point->axis_text_font;
+ if($point->axis_text_font_size)
+ $attr['font-size'] = $point->axis_text_font_size;
+ if($point->axis_text_font_weight)
+ $attr['font-weight'] = $point->axis_text_font_weight;
+ if($point->axis_text_colour)
+ $attr['fill'] = new Colour($this->graph, $point->axis_text_colour);
+ if($point->axis_text_back_colour)
+ $back_colour = [
+ 'stroke-width' => '3px',
+ 'stroke' => new Colour($this->graph, $point->axis_text_back_colour),
+ 'stroke-linejoin' => 'round',
+ ];
+ }
+
+ if(!empty($back_colour)) {
+ $b_attr = array_merge($back_colour, $attr);
+ $text_out .= $svg_text->text($string, $line_spacing, $b_attr);
+ }
+ $text_out .= $svg_text->text($string, $line_spacing, $attr);
+ return $text_out;
+ }
+
+ /**
+ * Returns text information:
+ * [Text, $font_size, $attr, $anchor, $rcx, $rcy, $angle, $line_spacing]
+ */
+ protected function getTextInfo($x, $y, &$point, $opposite, $level)
+ {
+ $font = $this->styles['t_font'];
+ $font_size = $this->styles['t_font_size'];
+ $font_adjust = $this->styles['t_font_adjust'];
+ if($point->item) {
+ if($point->axis_text_font)
+ $font = $point->axis_text_font;
+ if($point->axis_text_font_size)
+ $font_size = $point->axis_text_font_size;
+ if($point->axis_text_font_adjust)
+ $font_adjust = $point->axis_text_font_adjust;
+ }
+ $line_spacing = $this->styles['t_line_spacing'];
+ $svg_text = new Text($this->graph, $font, $font_adjust);
+ $baseline = $svg_text->baseline($font_size);
+ list($w, $h) = $svg_text->measure($point->getText($level), $font_size, 0,
+ $line_spacing);
+ if($this->orientation == 'h') {
+ $attr['x'] = $x + $point->position;
+ $attr['y'] = $y + $baseline - ($opposite ? $h : 0);
+ $anchor = 'middle';
+ } else {
+ $attr['x'] = $x;
+ $attr['y'] = $y + $baseline + $point->position - $h / 2;
+ $anchor = $opposite ? 'start' : 'end';
+ }
+
+ $angle = $this->styles['t_angle'];
+ $rcx = $rcy = null;
+ if($angle) {
+ if($this->orientation == 'h') {
+ $rcx = $x + $point->position;
+ $rcy = $y + $h * ($opposite ? -0.5 : 0.5);
+ if($angle < 0) {
+ $anchor = 'end';
+ $attr['x'] += $h * 0.5;
+ } else {
+ $anchor = 'start';
+ $attr['x'] -= $h * 0.5;
+ }
+ if($opposite)
+ $angle = -$angle;
+ } else {
+ $rcx = $attr['x'] + $h * ($opposite ? 0.5 : -0.5);
+ $rcy = $y + $point->position;
+ }
+ $xform = new Transform;
+ $xform->rotate($angle, $rcx, $rcy);
+ $attr['transform'] = $xform;
+ }
+ $attr['text-anchor'] = $anchor;
+
+ return [$svg_text, $font_size, $attr, $anchor, $rcx, $rcy, $angle,
+ $line_spacing];
+ }
+
+ /**
+ * Returns the path for divisions or subdivisions
+ */
+ protected function getDivisionPath($x, $y, $points, $path_info, $level)
+ {
+ $y0 = $y;
+ $x0 = $x;
+ $yinc = $xinc = 0;
+ if($this->orientation == 'v')
+ {
+ $x0 = $x + $path_info['pos'];
+ $len = $path_info['sz'];
+ $yinc = 1;
+ }
+ else
+ {
+ $y0 = $y - $path_info['pos'];
+ $len = -$path_info['sz'];
+ $xinc = 1;
+ }
+
+ $line = $path_info['line'];
+ $path = new PathData;
+ foreach($points as $point) {
+ $x = $x0 + $point->position * $xinc;
+ $y = $y0 + $point->position * $yinc;
+ $path->add('M', $x, $y, $line, $len);
+ }
+
+ if(!$path->isEmpty() && $path_info['box']) {
+ $x = $x0 + $path_info['box_pos'] * $yinc;
+ $y = $y0 + $path_info['box_pos'] * $xinc;
+ $path->add('M', $x, $y, $path_info['box_line'], $path_info['box_len']);
+ }
+ return $path;
+ }
+
+ /**
+ * Returns the details of the path segment
+ */
+ protected function getDivisionPathInfo($subdiv, $g_width, $g_height, $level)
+ {
+ if($this->orientation == 'h') {
+ $line = 'v';
+ $full = $g_height;
+ $box_len = $g_width;
+ $box_line = 'h';
+ } else {
+ $line = 'h';
+ $full = $g_width;
+ $box_len = $this->axis->reversed() ? -$g_height : $g_height;
+ $box_line = 'v';
+ }
+
+ if($subdiv) {
+ $style = $this->styles['s_style'];
+ $sz = $size = $this->styles['s_size'];
+ } else {
+ $style = $this->styles['d_style'];
+ $sz = $size = $this->styles['d_size'];
+ if($this->boxed_text) {
+ $bbox = $this->getTextBBox($level);
+ $sx = $bbox->width();
+ $sy = $bbox->height();
+ $sz = $size = ($this->orientation == 'h' ? $sy : $sx) +
+ $this->styles['t_space'];
+ }
+ }
+ if(!$this->main)
+ $style = str_replace('full', '', $style);
+ $pos = 0;
+ $box_pos = 0;
+ $box = false;
+
+ switch($style) {
+ case 'none' :
+ return null; // no pos or sz
+ case 'infull' :
+ $sz = $full;
+ break;
+ case 'over' :
+ $pos = -$size;
+ $sz = $size * 2;
+ break;
+ case 'overfull' :
+ if($this->axis_no == 0)
+ $pos = -$size;
+ $sz = $full + $size;
+ break;
+ case 'in' :
+ if($this->axis_no != 0)
+ $pos = -$size;
+ break;
+ case 'box' :
+ $box = true;
+ if($this->axis_no > 0)
+ $box_pos += ($this->orientation == 'h' ? -$size : $size);
+ if($this->axis_no == 0)
+ $pos -= $size;
+ break;
+ case 'extend' :
+ if($this->axis_no > 0)
+ $box_pos += ($this->orientation == 'h' ? -$size : $size);
+ // fall through
+ case 'out' :
+ default :
+ if($this->axis_no == 0)
+ $pos -= $size;
+ }
+
+ return compact('sz', 'pos', 'line', 'full', 'box', 'box_len', 'box_line',
+ 'box_pos');
+ }
+}
diff --git a/classes/vendor/81x/goat1000/svggraph/DisplayAxisLevels.php b/classes/vendor/81x/goat1000/svggraph/DisplayAxisLevels.php
new file mode 100644
index 0000000..1af13ab
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/DisplayAxisLevels.php
@@ -0,0 +1,303 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * An axis with multiple levels of divisions
+ */
+class DisplayAxisLevels extends DisplayAxis {
+
+ protected $level_count;
+ protected $levels;
+
+ /**
+ * Pass all these to parent constructor
+ */
+ public function __construct(&$graph, &$axis, $axis_no, $orientation, $type,
+ $main, $label_centre)
+ {
+ parent::__construct($graph, $axis, $axis_no, $orientation, $type, $main,
+ $label_centre);
+
+ if(!$this->show_text)
+ {
+ $this->levels = [null];
+ $this->level_count = 1;
+ return;
+ }
+
+ $levels = $graph->getOption(['axis_levels_' . $orientation, $axis_no]);
+ if(!is_array($levels))
+ $levels = array_fill(0, $levels, null);
+ $this->levels = $levels;
+ $this->level_count = count($levels);
+ }
+
+ /**
+ * Returns the extents of the axis, relative to where it will be drawn from
+ * returns BoundingBox
+ */
+ public function measure($with_label = true)
+ {
+ if($this->level_count <= 1)
+ return parent::measure($with_label);
+
+ $bbox = new BoundingBox(0, 0, 0, 0);
+ for($i = 0; $i < $this->level_count; ++$i) {
+ $dbox = $this->getDivisionsBBox($i);
+ $tbox = $this->getTextBBox($i);
+ if($this->orientation == 'h') {
+ $dbox->flipY();
+ if($this->axis_no > 0) {
+ $dbox->offset(0, $bbox->y1);
+ $tbox->offset(0, $bbox->y1);
+ } else {
+ $dbox->offset(0, $bbox->y2);
+ $tbox->offset(0, $bbox->y2);
+ }
+ } else {
+ if($this->axis_no > 0) {
+ $dbox->offset($bbox->x2, 0);
+ $tbox->offset($bbox->x2, 0);
+ } else {
+ $dbox->offset($bbox->x1, 0);
+ $tbox->offset($bbox->x1, 0);
+ }
+ }
+ // store the text box for this level
+ $this->levels[$i]['text_box'] = $tbox;
+
+ $bbox->growBox($tbox);
+ $bbox->growBox($dbox);
+ }
+
+ if($with_label && $this->show_label) {
+ $lpos = $this->getLabelPosition();
+ $bbox->grow($lpos['x'], $lpos['y'], $lpos['x'] + $lpos['width'],
+ $lpos['y'] + $lpos['height']);
+ }
+ $bbox = $this->addOffset($bbox);
+
+ return $bbox;
+ }
+
+ /**
+ * Returns the bounding box of the text
+ */
+ protected function getTextBBox($level)
+ {
+ if($this->level_count <= 1 || !$this->boxed_text)
+ return parent::getTextBBox($level);
+
+ $bbox = new BoundingBox(0, 0, 0, 0);
+ list($x_off, $y_off, $opp) = $this->getTextOffset(0, 0, 0, 0, 0, 0, $level);
+ $t_offset = ($this->orientation == 'h' ? $x_off : $y_off);
+ if($this->axis->reversed())
+ $t_offset = -$t_offset;
+ $length = $this->axis->getLength();
+
+ $points = $this->axis->getGridPoints(0);
+ $lpoints = $this->getPointsForLevel($points, $level);
+ $positions = $this->getTextPositions(0, 0, $x_off, $y_off, $lpoints, null);
+ $pcount = count($positions);
+ for($p = 0; $p < $pcount; ++$p) {
+ $point = $lpoints[$p];
+ if(!$this->pointTextVisible($point, $length, $t_offset))
+ continue;
+ if(!$point->blank($level)) {
+ $pos = $positions[$p];
+ $lbl = $this->measureText($pos['x'], $pos['y'], $point, $opp, $level);
+ $bbox->grow($lbl['x'], $lbl['y'], $lbl['x'] + $lbl['width'],
+ $lbl['y'] + $lbl['height']);
+ }
+ }
+ return $bbox;
+ }
+
+ /**
+ * Draws the axis text labels
+ */
+ public function drawText($x, $y, $gx, $gy, $g_width, $g_height)
+ {
+ if($this->level_count <= 1)
+ return parent::drawText($x, $y, $gx, $gy, $g_width, $g_height);
+
+ list($x_offset, $y_offset, $opposite) = $this->getTextOffset($x, $y,
+ $gx, $gy, $g_width, $g_height, 0);
+
+ $t_offset = ($this->orientation == 'h' ? $x_offset : $y_offset);
+ if($this->axis->reversed())
+ $t_offset = -$t_offset;
+
+ // measure function fills in text locations
+ $this->measure();
+ $labels = '';
+ $anchor = null;
+ $points = $this->axis->getGridPoints(0);
+ $length = $this->axis->getLength();
+
+ for($i = 0; $i < $this->level_count; ++$i) {
+ $count = count($points);
+ if($this->block_label)
+ --$count;
+
+ list($x_offset, $y_offset, $opposite) = $this->getTextOffset($x, $y,
+ $gx, $gy, $g_width, $g_height, $i);
+ $x1 = $x_offset;
+ $y1 = $y_offset;
+
+ $tbox = $this->levels[$i]['text_box'];
+ if($this->orientation == 'h') {
+ if($this->axis_no > 0)
+ $y1 = $y_offset + $tbox->y2;
+ else
+ $y1 = $y_offset + $tbox->y1;
+ switch($this->styles['t_align']) {
+ case 'left':
+ $anchor = 'start';
+ break;
+ case 'right':
+ $anchor = 'end';
+ break;
+ }
+ } else {
+ if($this->axis_no > 0)
+ $x1 = $x_offset + $tbox->x1;
+ else
+ $x1 = $x_offset + $tbox->x2;
+ switch($this->styles['t_align']) {
+ case 'left':
+ if(!$opposite) {
+ $anchor = 'start';
+ $x1 = $tbox->x1;
+ }
+ break;
+ case 'centre':
+ $x1 = ($tbox->x1 + $tbox->x2) / 2;
+ $anchor = 'middle';
+ break;
+ }
+ }
+
+ $lpoints = $this->getPointsForLevel($points, $i);
+ $positions = $this->getTextPositions($x, $y, $x1, $y1, $lpoints, $anchor);
+ $pcount = count($positions);
+ for($p = 0; $p < $pcount; ++$p) {
+ $point = $lpoints[$p];
+ if(!$this->pointTextVisible($point, $length, $t_offset))
+ continue;
+ $pos = $positions[$p];
+ $labels .= $this->getText($pos['x'], $pos['y'], $point, $opposite, $i, $anchor);
+ }
+ }
+ if($labels != '') {
+ $group = [
+ 'font-family' => $this->styles['t_font'],
+ 'font-size' => $this->styles['t_font_size'],
+ 'fill' => $this->styles['t_colour'],
+ ];
+ $weight = $this->styles['t_font_weight'];
+ if($weight != 'normal' && $weight !== null)
+ $group['font-weight'] = $weight;
+
+ $labels = $this->graph->element('g', $group, null, $labels);
+ }
+
+ if($this->show_label)
+ $labels .= $this->getLabel($x, $y, $gx, $gy, $g_width, $g_height);
+
+ return $labels;
+ }
+
+ /**
+ * Draws the axis divisions
+ */
+ public function drawDivisions($x, $y, $g_width, $g_height)
+ {
+ // parent can do simple divisions
+ if($this->level_count <= 1 || !$this->boxed_text)
+ return parent::drawDivisions($x, $y, $g_width, $g_height);
+
+ $path = '';
+ $points = $this->axis->getGridPoints(0);
+ $x_offset = $y_offset = 0;
+
+ // direction of offset depends on h/v and axis number
+ $direction = ($this->orientation == 'v' ? -1 : 1);
+ if($this->axis_no > 0)
+ $direction = -$direction;
+
+ for($i = 0; $i < $this->level_count; ++$i) {
+ $path_info = $this->getDivisionPathInfo(false, $g_width, $g_height, $i);
+ if($path_info === null)
+ continue;
+
+ $lpoints = $this->getPointsForLevel($points, $i);
+ $path .= $this->getDivisionPath($x + $x_offset, $y + $y_offset, $lpoints,
+ $path_info, $i);
+
+ if($this->orientation == 'h')
+ $y_offset += $direction * $path_info['sz'];
+ else
+ $x_offset += $direction * $path_info['sz'];
+ }
+ if($path == '')
+ return '';
+
+ $attr = [
+ 'd' => $path,
+ 'stroke' => $this->styles['d_colour'],
+ 'fill' => 'none',
+ ];
+ return $this->graph->element('path', $attr);
+ }
+
+ /**
+ * Returns the list of points with text for $level
+ */
+ protected function getPointsForLevel($points, $level)
+ {
+ // need first point even if it is blank
+ $lpoints = [$points[0]];
+ $prev = $points[0]->getText($level);
+
+ $last_point = count($points) - 1;
+ $end = $this->boxed_text ? $last_point : $last_point + 1;
+ for($p = 1; $p < $end; ++$p) {
+ if($points[$p]->blank($level))
+ continue;
+
+ $label = $points[$p]->getText($level);
+ if($label == $prev)
+ continue;
+
+ $lpoints[] = $points[$p];
+ $prev = $label;
+ }
+
+ // the last point is the division at the end of the axis
+ if($this->boxed_text)
+ $lpoints[] = $points[$last_point];
+ return $lpoints;
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/DisplayAxisRadar.php b/classes/vendor/81x/goat1000/svggraph/DisplayAxisRadar.php
new file mode 100644
index 0000000..c3e6240
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/DisplayAxisRadar.php
@@ -0,0 +1,287 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * Draws the radar graph X axis
+ */
+class DisplayAxisRadar extends DisplayAxis {
+
+ protected $xc;
+ protected $yc;
+ protected $radius;
+ protected $arad;
+ protected $text_offset;
+ protected $no_axis;
+ protected $direction = 1;
+
+ /**
+ * $orientation = 'h' or 'v'
+ * $type = 'x' or 'y'
+ * $main = TRUE for the main axis
+ */
+ public function __construct(&$graph, &$axis, $axis_no, $orientation, $type,
+ $main, $xc, $yc, $radius)
+ {
+ // only use this class for the round axis
+ if($orientation != 'h')
+ throw new \Exception('DisplayAxisRadar: orientation != "h"');
+ $this->xc = $xc;
+ $this->yc = $yc;
+ $this->radius = $radius;
+ $this->arad = (90 + $graph->getOption('start_angle')) * M_PI / 180;
+ $this->text_offset = 0;
+ // the radar-only option for hiding axis but showing divisions
+ $this->no_axis = !$graph->getOption('show_x_axis');
+
+ // $label_centre = TRUE because the end of the axis is also the start
+ parent::__construct($graph, $axis, $axis_no, $orientation, $type, $main,
+ true);
+
+ // no boxed text because this isn't really a block-labelled axis
+ $this->boxed_text = false;
+
+ if($graph->getOption('reverse'))
+ $this->direction = -1;
+
+ // keep text next to axis
+ $this->styles['t_location'] = 'axis';
+ }
+
+ /**
+ * Returns the extents of the axis, relative to where it will be drawn from
+ * returns array('x', 'y', 'width', 'height')
+ */
+ public function measure($with_label = true)
+ {
+ $bbox = new BoundingBox($this->xc, $this->yc, $this->xc, $this->yc);
+ if($this->show_axis) {
+
+ // find radius including division marks
+ $d = 0;
+ if($this->show_divisions) {
+ $d_info = $this->getDivisionPathInfo(false, 0, 0, 0);
+ if($d_info['pos'] < 0)
+ $d = abs($d_info['pos']);
+ if($this->show_subdivisions) {
+ $s_info = $this->getDivisionPathInfo(true, 0, 0, 0);
+ if($s_info['pos'] < 0) {
+ $s = abs($s_info['pos']);
+ if($s > $d)
+ $d = $s;
+ }
+ }
+ }
+ $r = $this->radius + $d;
+ $bbox->grow($this->xc - $r, $this->yc - $r, $this->xc + $r, $this->yc + $r);
+ }
+
+ if($this->show_text) {
+ list($x_off, $y_off, $opp) = $this->getTextOffset(0, 0, 0, 0, 0, 0, 0);
+
+ $points = $this->axis->getGridPoints(0);
+ $count = count($points);
+ if($this->block_label)
+ --$count;
+ for($p = 0; $p < $count; ++$p) {
+
+ if($points[$p]->blank())
+ continue;
+
+ $lbl = $this->measureText($x_off, $y_off, $points[$p], $opp, 0);
+ $bbox->grow($lbl['x'], $lbl['y'], $lbl['x'] + $lbl['width'],
+ $lbl['y'] + $lbl['height']);
+ }
+ }
+
+ if($with_label && $this->show_label) {
+ $lpos = $this->getLabelPosition();
+ $bbox->grow($lpos['x'], $lpos['y'], $lpos['x'] + $lpos['width'],
+ $lpos['y'] + $lpos['height']);
+ }
+
+ return $bbox;
+ }
+
+ /**
+ * Draws the axis line
+ */
+ public function drawAxisLine($x, $y, $len)
+ {
+ // the 'show_x_axis' option turns the line off
+ if($this->no_axis)
+ return '';
+ $points = [new GridPoint($this->radius, '', 0)];
+ $attr = [
+ 'stroke' => $this->styles['colour'],
+ 'd' => $this->graph->yGrid($points),
+ 'fill' => 'none',
+ ];
+
+ if($this->styles['stroke_width'] != 1)
+ $attr['stroke-width'] = $this->styles['stroke_width'];
+ return $this->graph->element('path', $attr);
+ }
+
+ /**
+ * Returns the path for divisions or subdivisions
+ */
+ protected function getDivisionPath($x, $y, $points, $path_info, $level)
+ {
+ $path = new PathData;
+ $len = -$path_info['sz'];
+ $r1 = $this->radius - $path_info['pos'];
+ foreach($points as $p) {
+ $a = $this->arad + $this->direction * $p->position / $this->radius;
+ $x1 = $x + $r1 * sin($a);
+ $y1 = $y + $r1 * cos($a);
+ $x2 = $len * sin($a);
+ $y2 = $len * cos($a);
+ $path->add('M', $x1, $y1, 'l', $x2, $y2);
+ }
+ if(!$path->isEmpty() && $path_info['box']) {
+ $points = [new GridPoint($this->radius + $path_info['sz'], '', 0)];
+ $path->add($this->graph->yGrid($points));
+ }
+ return $path;
+ }
+
+ /**
+ * Returns the distance from the axis to draw the text
+ */
+ protected function getTextOffset($ax, $ay, $gx, $gy, $g_width, $g_height, $level)
+ {
+ list($x, $y, $opposite) = parent::getTextOffset($ax, $ay, $gx, $gy,
+ $g_width, $g_height, $level);
+ $this->text_offset = $y;
+ return [$x, $y, $opposite];
+ }
+
+ /**
+ * Returns text information:
+ * [Text, $font_size, $attr, $anchor, $rcx, $rcy, $angle, $line_spacing]
+ */
+ protected function getTextInfo($x, $y, &$point, $opposite, $level)
+ {
+ $a = $this->arad + $this->direction * $point->position / $this->radius;
+ $r1 = $this->radius + $this->text_offset;
+ $x1 = $this->xc + $r1 * sin($a);
+ $y1 = $this->yc + $r1 * cos($a);
+ $text_angle = $this->styles['t_angle'];
+
+ $tau = 2 * M_PI;
+ while($a < 0)
+ $a += $tau;
+ $a = fmod($a, $tau);
+
+ $t_angle = deg2rad($text_angle);
+ while($t_angle < 0)
+ $t_angle += $tau;
+
+ $sector = floor(($a + $t_angle) * 15.999 / $tau) % 16;
+ // text anchor depends on angle between text and axis
+ $left = $opposite ? 'start' : 'end';
+ $right = $opposite ? 'end' : 'start';
+ $anchor = $right;
+ if($sector == 0 || $sector == 7 || $sector == 8 || $sector == 15)
+ $anchor = 'middle';
+ elseif($sector > 8)
+ $anchor = $left;
+ $top = ($opposite ? $sector == 7 || $sector == 8 :
+ $sector == 0 || $sector == 15);
+
+ $font_size = $this->styles['t_font_size'];
+ $line_spacing = $this->styles['t_line_spacing'];
+ $svg_text = new Text($this->graph, $this->styles['t_font'],
+ $this->styles['t_font_adjust']);
+ $baseline = $svg_text->baseline($font_size);
+ list($w, $h) = $svg_text->measure($point->getText(), $font_size, 0,
+ $line_spacing);
+
+ $attr['x'] = $x1;
+ if($anchor == 'middle')
+ $attr['y'] = $y1 + ($top ? $baseline : 0);
+ else
+ $attr['y'] = $y1 + $baseline - $h / 2;
+
+ $rcx = $rcy = null;
+ if($text_angle) {
+ $rcx = $x1;
+ $rcy = $y1;
+ $xform = new Transform;
+ $xform->rotate($text_angle, $rcx, $rcy);
+ $attr['transform'] = $xform;
+ }
+ $attr['text-anchor'] = $anchor;
+ return [$svg_text, $font_size, $attr, $anchor, $rcx, $rcy, $text_angle,
+ $line_spacing];
+ }
+
+ /**
+ * Returns the label
+ */
+ protected function getLabel($x, $y, $gx, $gy, $g_width, $g_height)
+ {
+ // GetLabelPosition returns absolute text position, so ignore $x and $y
+ return parent::getLabel(0, 0, $gx, $gy, $g_width, $g_height);
+ }
+
+ /**
+ * Override position correction
+ */
+ protected function getLabelOffset($x, $y, $gx, $gy, $g_width, $g_height)
+ {
+ // no need for correction
+ return ['x' => $x, 'y' => $y];
+ }
+
+ /**
+ * Returns the dimensions of the label
+ * x, y, width, height = position and size
+ * tx, tx = text anchor point
+ * angle = text angle
+ */
+ protected function getLabelPosition()
+ {
+ $bbox = $this->measure(false);
+ $font_size = $this->styles['l_font_size'];
+ $line_spacing = $this->styles['l_line_spacing'];
+ $svg_text = new Text($this->graph, $this->styles['l_font']);
+ $tsize = $svg_text->measure($this->label, $font_size, 0, $line_spacing);
+ $baseline = $svg_text->baseline($font_size);
+ $space = $this->styles['l_space'];
+
+ $width = $tsize[0];
+ $height = $tsize[1];
+ $y = $bbox->y2 + $space;
+ $tx = $this->xc;
+ $ty = $y + $baseline;
+ $x = $tx - $width / 2;
+
+ $height += $space;
+ $angle = 0;
+ $res = compact('x', 'y', 'width', 'height', 'tx', 'ty', 'angle', 'bbox');
+ return $res;
+ }
+
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/DisplayAxisRotated.php b/classes/vendor/81x/goat1000/svggraph/DisplayAxisRotated.php
new file mode 100644
index 0000000..379f728
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/DisplayAxisRotated.php
@@ -0,0 +1,244 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * A rotated axis
+ */
+class DisplayAxisRotated extends DisplayAxis {
+
+ protected $arad;
+ protected $anchor;
+ protected $top;
+ protected $label_offset = 0;
+ protected $label_angle = 0;
+
+ /**
+ * $arad = angle in radians
+ */
+ public function __construct(&$graph, &$axis, $axis_no, $orientation, $type,
+ $main, $arad)
+ {
+ if($orientation != 'v')
+ throw new \Exception('DisplayAxisRotated: orientation != "v"');
+ $this->arad = $arad;
+ $this->anchor = 'end';
+ $this->top = false;
+ parent::__construct($graph, $axis, $axis_no, $orientation, $type, $main,
+ false);
+
+ // rotated axis can't position text at edge of grid
+ $this->styles['t_location'] = 'axis';
+ }
+
+ /**
+ * Draws the axis line
+ */
+ public function drawAxisLine($x, $y, $len)
+ {
+ $overlap = $this->styles['overlap'];
+ $length = $len + $overlap * 2;
+ $x0 = $x - $overlap * sin($this->arad);
+ $y0 = $y - $overlap * cos($this->arad);
+ $x1 = $length * sin($this->arad);
+ $y1 = $length * cos($this->arad);
+
+ $attr = [
+ 'stroke' => $this->styles['colour'],
+ 'd' => new PathData('M', $x0, $y0, 'l', $x1, $y1),
+ ];
+
+ if($this->styles['stroke_width'] != 1)
+ $attr['stroke-width'] = $this->styles['stroke_width'];
+ return $this->graph->element('path', $attr);
+ }
+
+ /**
+ * Returns the path for divisions or subdivisions
+ */
+ protected function getDivisionPath($x, $y, $points, $path_info, $level)
+ {
+ $a = $this->arad + ($this->arad <= M_PI_2 ? - M_PI_2 : M_PI_2);
+ $path = new PathData;
+ $c = cos($this->arad);
+ $s = sin($this->arad);
+ $px = $path_info['pos'] * sin($a);
+ $py = $path_info['pos'] * cos($a);
+ $x2 = $path_info['sz'] * sin($a);
+ $y2 = $path_info['sz'] * cos($a);
+ if($this->type == 'y')
+ {
+ $px = -$px;
+ $py = -$py;
+ $x2 = -$x2;
+ $y2 = -$y2;
+ }
+ foreach($points as $pt) {
+ $x1 = ($x - $pt->position * $s) + $px;
+ $y1 = ($y - $pt->position * $c) + $py;
+ $path->add('M', $x1, $y1, 'l', $x2, $y2);
+ }
+ if($path != '' && $path_info['box']) {
+ $x1 = abs($path_info['box_len']) * $s;
+ $y1 = abs($path_info['box_len']) * $c;
+ $x -= $x2;
+ $y -= $y2;
+ $path->add('M', $x, $y, 'l', $x1, $y1);
+ }
+ return $path;
+ }
+
+ /**
+ * Calculates the rotated offset
+ */
+ protected function getTextOffset($ax, $ay, $gx, $gy, $g_width, $g_height, $level)
+ {
+ list($x, $y, $opposite) = parent::getTextOffset($ax, $ay, $gx, $gy,
+ $g_width, $g_height, $level);
+
+ $tau = 2 * M_PI;
+ $a = $this->arad + M_PI_2;
+ while($a < 0)
+ $a += $tau;
+ $a = fmod($a, $tau);
+
+ $t_angle = deg2rad($this->styles['t_angle']);
+ while($t_angle < 0)
+ $t_angle += $tau;
+
+ $sector = floor(($a + $t_angle) * 15.999 / $tau) % 16;
+ $c = cos($a);
+ $s = sin($a);
+ $len = $x;
+ $x = -$len * $s;
+ $y = -$len * $c;
+
+ // text anchor depends on angle between text and axis
+ $left = $opposite ? 'start' : 'end';
+ $right = $opposite ? 'end' : 'start';
+ $this->anchor = $right;
+ if($sector == 0 || $sector == 7 || $sector == 8 || $sector == 15)
+ $this->anchor = 'middle';
+ elseif($sector > 8)
+ $this->anchor = $left;
+ $this->top = ($opposite ? $sector == 7 || $sector == 8 :
+ $sector == 0 || $sector == 15);
+ return [$x, $y, $opposite];
+ }
+
+ /**
+ * Returns text information:
+ * [Text, $font_size, $attr, $anchor, $rcx, $rcy, $angle, $line_spacing]
+ */
+ protected function getTextInfo($x, $y, &$point, $opposite, $level)
+ {
+ $direction = $this->type == 'y' ? -1 : 1;
+ $x_add = $point->position * $direction * sin($this->arad);
+ $y_add = $point->position * $direction * cos($this->arad);
+
+ $font_size = $this->styles['t_font_size'];
+ $line_spacing = $this->styles['t_line_spacing'];
+ $svg_text = new Text($this->graph, $this->styles['t_font'],
+ $this->styles['t_font_adjust']);
+ $baseline = $svg_text->baseline($font_size);
+ list($w, $h) = $svg_text->measure($point->getText(), $font_size, 0,
+ $line_spacing);
+ $attr = [
+ 'x' => $x + $x_add,
+ 'text-anchor' => $this->anchor,
+ ];
+
+ if($this->anchor == 'middle')
+ $attr['y'] = $y + $y_add + ($this->top ? $baseline : 0);
+ else
+ $attr['y'] = $y + $y_add + $baseline - $h / 2;
+
+ $angle = $this->styles['t_angle'];
+ $rcx = $rcy = null;
+ if($angle) {
+ $rcx = $x + $x_add;
+ $rcy = $y + $y_add;
+ $xform = new Transform;
+ $xform->rotate($angle, $rcx, $rcy);
+ $attr['transform'] = $xform;
+ }
+ return [$svg_text, $font_size, $attr, $this->anchor, $rcx, $rcy, $angle,
+ $line_spacing];
+ }
+
+ /**
+ * Override position correction
+ */
+ protected function getLabelOffset($x, $y, $gx, $gy, $g_width, $g_height)
+ {
+ // no need for correction
+ return ['x' => $x, 'y' => $y];
+ }
+
+ /**
+ * Returns the dimensions of the label
+ */
+ protected function getLabelPosition()
+ {
+ $font_size = $this->styles['l_font_size'];
+ $line_spacing = $this->styles['l_line_spacing'];
+ $svg_text = new Text($this->graph, $this->styles['l_font']);
+ $tsize = $svg_text->measure($this->label, $font_size, 0, $line_spacing);
+ $baseline = $svg_text->baseline($font_size);
+ $c = cos($this->arad);
+ $s = sin($this->arad);
+
+ // use plain axis for calculating distance from axis
+ $plain = new DisplayAxis($this->graph, $this->axis, $this->axis_no,
+ $this->orientation, $this->type, $this->main, false);
+ $bbox = $plain->measure(false);
+ $space = $this->styles['l_space'];
+
+ if($s < 0) {
+ $offset = $bbox->x2 + $space + $tsize[1] - $baseline;
+ $angle = 180 - (rad2deg($this->arad) - 90);
+ } else {
+ $offset = -$bbox->x1 + $space + $tsize[1] - $baseline;
+ $angle = - (rad2deg($this->arad) - 90);
+ }
+
+ $a = $this->arad + M_PI_2;
+ $x2 = $offset * sin($a);
+ $y2 = $offset * cos($a);
+ $p = $this->axis->getLength() / 2;
+ $tx = $p * sin($this->arad) + $x2;
+ $ty = $p * cos($this->arad) + $y2;
+
+ // these don't matter - the text is over the graph anyway
+ $x = $y = $width = $height = 0;
+
+ return compact('x', 'y', 'width', 'height', 'tx', 'ty', 'angle');
+ }
+
+ /**
+ * Returns true if the text exists
+ */
+ protected function pointTextVisible($point, $axis_len, $offset)
+ {
+ return !$point->blank();
+ }
+}
diff --git a/classes/vendor/81x/goat1000/svggraph/Donut3DGraph.php b/classes/vendor/81x/goat1000/svggraph/Donut3DGraph.php
new file mode 100644
index 0000000..0e32fb8
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/Donut3DGraph.php
@@ -0,0 +1,80 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class Donut3DGraph extends Pie3DGraph {
+
+ use DonutGraphTrait;
+ protected $edge_class = 'Goat1000\\SVGGraph\\DonutSliceEdge';
+
+ public function __construct($w, $h, array $settings, array $fixed_settings = [])
+ {
+ $fs = [];
+ // enable flat sides when drawing a gap
+ if(isset($settings['donut_slice_gap']) && $settings['donut_slice_gap'] > 0)
+ $fs['draw_flat_sides'] = true;
+
+ $fs = array_merge($fs, $fixed_settings);
+ parent::__construct($w, $h, $settings, $fs);
+ }
+
+ /**
+ * Returns the gradient overlay
+ */
+ protected function getEdgeOverlay($x_centre, $y_centre, $depth, $clip_path, $edge)
+ {
+ if(!$edge->inner())
+ return parent::getEdgeOverlay($x_centre, $y_centre, $depth, $clip_path, $edge);
+
+ // use radius of whole pie unless slice values are set
+ $radius_x = $this->radius_x;
+ $radius_y = $this->radius_y;
+ if($edge->slice['radius_x'] && $edge->slice['radius_y']) {
+ $radius_x = $edge->slice['radius_x'];
+ $radius_y = $edge->slice['radius_y'];
+ }
+
+ $ratio = $edge->getInnerRatio();
+ $radius_x *= $ratio;
+ $radius_y *= $ratio;
+
+ // clip a gradient-filled rect to the edge shape
+ $cx = new Number($x_centre);
+ $cy = new Number($y_centre);
+ $gradient_id = $this->defs->addGradient($this->getOption('depth_shade_gradient'));
+ $rect = [
+ 'x' => $x_centre - $radius_x,
+ 'y' => $y_centre - $radius_y,
+ 'width' => $radius_x * 2.0,
+ 'height' => $radius_y * 2.0 + $this->depth + 2.0,
+ 'fill' => 'url(#' . $gradient_id . ')',
+
+ // rotate the rect to reverse the gradient
+ 'transform' => "rotate(180,{$cx},{$cy})",
+ ];
+
+ // clip a group containing the rotated rect
+ $g = [ 'clip-path' => 'url(#' . $clip_path . ')' ];
+ return $this->element('g', $g, null, $this->element('rect', $rect));
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/DonutGraph.php b/classes/vendor/81x/goat1000/svggraph/DonutGraph.php
new file mode 100644
index 0000000..a22078b
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/DonutGraph.php
@@ -0,0 +1,28 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class DonutGraph extends PieGraph {
+
+ use DonutGraphTrait;
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/DonutGraphTrait.php b/classes/vendor/81x/goat1000/svggraph/DonutGraphTrait.php
new file mode 100644
index 0000000..6d8ba4e
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/DonutGraphTrait.php
@@ -0,0 +1,168 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+trait DonutGraphTrait {
+
+ /**
+ * Returns the outer and inner angle adjustments for the slice gap
+ */
+ public function getSliceGap($angle, $ratio)
+ {
+ $gap_angle = $this->getOption('donut_slice_gap');
+ $outer_a = $inner_a = 0;
+ if($gap_angle > 0) {
+ $a = deg2rad($gap_angle);
+ if($a < $angle * 0.5) {
+ $outer_a = 0.5 * $a;
+ $inner_a = $outer_a / $ratio;
+ }
+ }
+ return [$outer_a, $inner_a];
+ }
+
+ /**
+ * Override the parent to draw doughnut slice
+ */
+ protected function getSlice($item, $angle_start, $angle_end, $radius_x,
+ $radius_y, &$attr, $single_slice, $colour_index)
+ {
+ $ratio = min(0.99, max(0.01, $this->getOption('inner_radius')));
+ $angle = $angle_end - $angle_start;
+
+ list($outer_a, $inner_a) = $this->getSliceGap($angle, $ratio);
+ $x_start = $y_start = $x_end = $y_end = 0;
+ $angle_start += $this->s_angle;
+ $angle_end += $this->s_angle;
+ $this->calcSlice($angle_start + $outer_a, $angle_end - $outer_a,
+ $radius_x, $radius_y, $x_start, $y_start, $x_end, $y_end);
+ $xc = $this->x_centre;
+ $yc = $this->y_centre;
+ $rx1 = $radius_x * $ratio;
+ $ry1 = $radius_y * $ratio;
+
+ if($single_slice && $this->full_angle >= M_PI * 2.0) {
+ $x1_start = $xc + $rx1;
+ $x1_end = $xc - $rx1;
+ $y1_start = $y1_end = $yc;
+ $x2_start = $xc + $radius_x;
+ $x2_end = $xc - $radius_x;
+ $y2_start = $y2_end = $yc;
+ // path with ellipses made of arcs
+ $path = new PathData('M', $x1_start, $y1_start);
+ $path->add('A', $rx1, $ry1, 0, 0, 0, $x1_end, $y1_end);
+ $path->add('A', $rx1, $ry1, 0, 0, 0, $x1_start, $y1_start);
+ $path->add('M', $x2_start, $y2_start);
+ $path->add('A', $radius_x, $radius_y, 0, 0, 0, $x2_end, $y2_end);
+ $path->add('A', $radius_x, $radius_y, 0, 0, 0, $x2_start, $y2_start);
+ $attr['d'] = $path;
+ $attr['fill-rule'] = 'evenodd';
+ } else {
+ $outer = ($angle > M_PI ? 1 : 0);
+ $sweep = ($this->getOption('reverse') ? 0 : 1);
+
+ // inner radius reduced by gap
+ $as = $angle_start + $inner_a;
+ $ae = $angle_end - $inner_a;
+ if($ae < $as)
+ $ae = $as = ($ae + $as) / 2; // not enough space, so come to a point
+
+ $this->calcSlice($as, $ae, $rx1, $ry1, $x1_start, $y1_start, $x1_end, $y1_end);
+ $isweep = $sweep ? 0 : 1;
+ $path = new PathData('M', $x1_end, $y1_end);
+ $path->add('A', $rx1, $ry1, 0, $outer, $isweep, $x1_start, $y1_start);
+ $path->add('L', $x_start, $y_start);
+ $path->add('A', $radius_x, $radius_y, 0, $outer, $sweep, $x_end, $y_end, 'z');
+ $attr['d'] = $path;
+ }
+ return $this->element('path', $attr);
+ }
+
+ /**
+ * Returns extra drawing code that goes between pie and labels
+ */
+ protected function pieExtras()
+ {
+ $inner_text = $this->getOption('inner_text');
+ if(empty($inner_text))
+ return '';
+
+ // use content label for inner text - measurements don't really matter
+ $this->addContentLabel('innertext', 0,
+ $this->x_centre - 100, $this->y_centre - 100, 200, 200,
+ $inner_text);
+ return '';
+ }
+
+ /**
+ * Overridden to keep inner text in the middle
+ */
+ public function dataLabelPosition($dataset, $index, &$item, $x, $y, $w, $h,
+ $label_w, $label_h)
+ {
+ if($dataset === 'innertext')
+ return ['centre middle', [$x, $y] ];
+
+ list($pos, $target) = parent::dataLabelPosition($dataset, $index, $item,
+ $x, $y, $w, $h, $label_w, $label_h);
+ if(isset($this->slice_info[$index]) && $this->getOption('label_position') <= 1) {
+ $a = $this->slice_info[$index]->midAngle();
+ $ac = $this->s_angle + $a;
+ $rx = $this->slice_info[$index]->radius_x;
+ $ry = $this->slice_info[$index]->radius_y;
+ $ring_centre = ($this->getOption('inner_radius') + 1) * 0.5;
+ $xt = $rx * $ring_centre * cos($ac);
+ $yt = ($this->getOption('reverse') ? -1 : 1) * $ry * $ring_centre * sin($ac);
+ $target = [$x + $xt, $y + $yt];
+ }
+ return [$pos, $target];
+ }
+
+ /**
+ * Returns the style options for the inner text label
+ */
+ public function dataLabelStyle($dataset, $index, &$item)
+ {
+ $style = parent::dataLabelStyle($dataset, $index, $item);
+
+ if($dataset !== 'innertext')
+ return $style;
+
+ // label settings can override global settings
+ $opts = [
+ 'font' => 'inner_text_font',
+ 'font_size' => 'inner_text_font_size',
+ 'font_weight' => 'inner_text_font_weight',
+ 'font_adjust' => 'inner_text_font_adjust',
+ 'colour' => 'inner_text_colour',
+ 'back_colour' => 'inner_text_back_colour',
+ ];
+ foreach($opts as $key => $opt)
+ if(isset($this->settings[$opt]) && !empty($this->settings[$opt]))
+ $style[$key] = $this->settings[$opt];
+
+ // no boxes
+ $style['type'] = 'plain';
+ return $style;
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/DonutSliceEdge.php b/classes/vendor/81x/goat1000/svggraph/DonutSliceEdge.php
new file mode 100644
index 0000000..d16f5a0
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/DonutSliceEdge.php
@@ -0,0 +1,233 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * The DonutSliceEdge class calculates and draws the 3D slice edges
+ */
+class DonutSliceEdge extends PieSliceEdge {
+
+ protected $ratio = 1.0;
+ protected $outer_a = 0;
+ protected $inner_a = 0;
+
+ /**
+ * $slice is the slice details array
+ * $s_angle is the start angle in radians
+ */
+ public function __construct(&$graph, $type, $slice, $s_angle)
+ {
+ // types: 0 => start flat, 1 => end flat, 2, 3, 4, 5 => curves, -1 => no edge
+ $this->type = -1;
+ $this->slice = $slice;
+ $tau = M_PI * 2.0;
+
+ $start_angle = $slice['angle_start'] + $s_angle;
+ $end_angle = $slice['angle_end'] + $s_angle;
+ $ratio = min(0.99, max(0.01, $graph->getOption('inner_radius')));
+ list($outer_a, $inner_a) = $graph->getSliceGap($end_angle - $start_angle, $ratio);
+ $this->outer_a = $outer_a;
+ $this->inner_a = $inner_a;
+
+ if(isset($slice['single_slice']) && $slice['single_slice'] &&
+ !is_numeric($graph->end_angle)) {
+
+ // full pie, draw full bottom and inner edges only
+ switch($type)
+ {
+ case 2:
+ $start_angle = 0.0;
+ $end_angle = M_PI;
+ break;
+ case 4:
+ $start_angle = M_PI;
+ $end_angle = $tau;
+ break;
+ default:
+ return;
+ }
+ } elseif($graph->getOption('reverse')) {
+ // apply reverse now to save thinking about it later
+ $s = M_PI * 4.0 - $end_angle;
+ $e = M_PI * 4.0 - $start_angle;
+ $start_angle = $s;
+ $end_angle = $e;
+ }
+
+ $this->a1 = fmod($start_angle, $tau);
+ $this->a2 = fmod($end_angle, $tau);
+ if($this->a2 < $this->a1)
+ $this->a2 += $tau;
+
+ switch($type) {
+ case 0:
+ // flat edge at a1
+ $this->a2 = $this->a1;
+ $this->ratio = $ratio;
+ break;
+ case 1:
+ // flat edge at a2
+ $this->a1 = $this->a2;
+ $this->ratio = $ratio;
+ $this->outer_a = -$outer_a;
+ $this->inner_a = -$inner_a;
+ break;
+ case 2:
+ // bottom edge
+ if($this->a1 > M_PI && $this->a2 < $tau)
+ return;
+ // truncate curves to visible area
+ if($this->a1 <= M_PI && $this->a2 >= M_PI)
+ $this->a2 = M_PI;
+ elseif($this->a1 > M_PI && $this->a2 > $tau)
+ $this->a1 = $tau;
+ if($this->a2 > M_PI * 3.0)
+ $this->a2 = M_PI * 3.0;
+ break;
+ case 3:
+ // type 3 edges are where the slice starts bottom, goes through top and ends at bottom
+ if($this->a2 < $tau || $this->a2 > M_PI * 3.0 || $this->a1 >= M_PI)
+ return;
+
+ $this->a1 = $tau;
+ break;
+ case 4:
+ // slices passing through top
+ if($this->a2 <= M_PI)
+ return;
+
+ if($this->a1 < M_PI)
+ $this->a1 = M_PI;
+ if($this->a2 > $tau)
+ $this->a2 = $tau;
+ $this->ratio = $ratio;
+ break;
+ case 5:
+ // slice starts at top, passes through bottom and ends at top
+ if($this->a2 < M_PI * 3.0)
+ return;
+
+ $this->a1 = M_PI * 3.0;
+ $this->ratio = $ratio;
+ break;
+ }
+
+ // ignore tiny curves as floating point artifacts
+ if($type > 1 && abs($this->a1 - $this->a2) < 0.0001)
+ return;
+
+ $this->setupSort();
+ $this->type = $type;
+ }
+
+ /**
+ * Returns the number of edge types this class supports
+ */
+ protected static function getEdgeTypes()
+ {
+ return 5;
+ }
+
+ /**
+ * Returns TRUE when the edge is visible
+ */
+ public function visible()
+ {
+ switch($this->type)
+ {
+ case -1:
+ return false; // type -1 is for non-existent edges
+ case 0:
+ // start on right not visible
+ if($this->a1 < M_PI * 0.5 || $this->a1 > M_PI * 1.5)
+ return false;
+ break;
+ case 1:
+ // end on left not visible
+ $a2 = fmod($this->a2, M_PI * 2.0);
+ if($a2 > M_PI * 0.5 && $a2 < M_PI * 1.5)
+ return false;
+ break;
+ }
+
+ // curves always visible on donut graph
+ return true;
+ }
+
+ /**
+ * Returns TRUE when this is an inner edge
+ */
+ public function inner()
+ {
+ return $this->type > 3;
+ }
+
+ /**
+ * Returns the ratio of inner to outer
+ */
+ public function getInnerRatio()
+ {
+ return $this->ratio;
+ }
+
+ /**
+ * Returns the path for a flat edge
+ */
+ protected function getFlatPath($angle, $x_centre, $y_centre, $depth)
+ {
+ $rx1 = $this->slice['radius_x'] * cos($angle + $this->outer_a);
+ $ry1 = $this->slice['radius_y'] * sin($angle + $this->outer_a);
+ $rx2 = $this->slice['radius_x'] * $this->ratio * cos($angle + $this->inner_a);
+ $ry2 = $this->slice['radius_y'] * $this->ratio * sin($angle + $this->inner_a);
+ $x1 = $x_centre + $rx1;
+ $y1 = $y_centre + $ry1;
+ $x2 = $x_centre + $rx2;
+ $y2 = $y_centre + $ry2;
+ return new PathData('M', $x2, $y2, 'v', $depth, 'L', $x1, $y1 + $depth,
+ 'v', -$depth, 'z');
+ }
+
+ /**
+ * Returns the path for the curved edge
+ */
+ protected function getCurvedPath($x_centre, $y_centre, $depth)
+ {
+ $a = $this->ratio < 1.0 ? $this->inner_a : $this->outer_a;
+ $rx = $this->slice['radius_x'] * $this->ratio;
+ $ry = $this->slice['radius_y'] * $this->ratio;
+ $x1 = $x_centre + $rx * cos($this->a1 + $a);
+ $y1 = $y_centre + $ry * sin($this->a1 + $a);
+ $x2 = $x_centre + $rx * cos($this->a2 - $a);
+ $y2 = $y_centre + $ry * sin($this->a2 - $a);
+ $y2d = $y2 + $depth;
+
+ $outer = 0; // edge is never > PI
+ $sweep = 1;
+
+ $path = new PathData('M', $x1, $y1, 'v', $depth, 'A', $rx, $ry, 0,
+ $outer, $sweep, $x2, $y2d, 'v', -$depth);
+ $sweep = $sweep ? 0 : 1;
+ $path->add('A', $rx, $ry, 0, $outer, $sweep, $x1, $y1);
+ return $path;
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/Ellipse.php b/classes/vendor/81x/goat1000/svggraph/Ellipse.php
new file mode 100644
index 0000000..8eaa0b8
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/Ellipse.php
@@ -0,0 +1,31 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class Ellipse extends Shape {
+ protected $element = 'ellipse';
+ protected $required = ['cx','cy','rx','ry'];
+ protected $transform = ['rx' => 'x', 'ry' => 'y'];
+ protected $transform_from = ['rx' => 'cx', 'ry' => 'cy'];
+ protected $transform_pairs = [ ['cx', 'cy'] ];
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/EmptyGraph.php b/classes/vendor/81x/goat1000/svggraph/EmptyGraph.php
new file mode 100644
index 0000000..71da72c
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/EmptyGraph.php
@@ -0,0 +1,56 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class EmptyGraph extends Graph {
+
+ /**
+ * Does nothing, no colours to set up
+ */
+ protected function setup()
+ {
+ }
+
+ /**
+ * Draws an empty graph
+ */
+ protected function draw()
+ {
+ // maybe not completely empty
+ return $this->underShapes() . $this->overShapes();
+ }
+
+ /**
+ * Ignore values, not used on empty graph
+ */
+ public function values($values)
+ {
+ }
+
+ /**
+ * Drawing nothing, so check nothing
+ */
+ protected function checkValues()
+ {
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/ExplodedDonut3DGraph.php b/classes/vendor/81x/goat1000/svggraph/ExplodedDonut3DGraph.php
new file mode 100644
index 0000000..11981a1
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/ExplodedDonut3DGraph.php
@@ -0,0 +1,47 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class ExplodedDonut3DGraph extends Donut3DGraph {
+
+ use ExplodedPieGraphTrait;
+
+ public function __construct($w, $h, array $settings, array $fixed_settings = [])
+ {
+ $fs = [ 'draw_flat_sides' => true, ];
+ $fs = array_merge($fs, $fixed_settings);
+ parent::__construct($w, $h, $settings, $fs);
+ }
+
+ /**
+ * Returns an edge markup
+ */
+ protected function getEdge($edge, $x_centre, $y_centre, $depth, $overlay)
+ {
+ list($xo, $yo) = $this->pie_exploder->getExplode($edge->slice['item'],
+ $edge->slice['angle_start'] + $this->s_angle,
+ $edge->slice['angle_end'] + $this->s_angle);
+ return parent::getEdge($edge, $x_centre + $xo, $y_centre + $yo, $depth,
+ $overlay);
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/ExplodedDonutGraph.php b/classes/vendor/81x/goat1000/svggraph/ExplodedDonutGraph.php
new file mode 100644
index 0000000..09d7c63
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/ExplodedDonutGraph.php
@@ -0,0 +1,29 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class ExplodedDonutGraph extends DonutGraph {
+
+ use ExplodedPieGraphTrait;
+
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/ExplodedPie3DGraph.php b/classes/vendor/81x/goat1000/svggraph/ExplodedPie3DGraph.php
new file mode 100644
index 0000000..1913ccd
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/ExplodedPie3DGraph.php
@@ -0,0 +1,47 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class ExplodedPie3DGraph extends Pie3DGraph {
+
+ use ExplodedPieGraphTrait;
+
+ public function __construct($w, $h, array $settings, array $fixed_settings = [])
+ {
+ $fs = [ 'draw_flat_sides' => true, ];
+ $fs = array_merge($fs, $fixed_settings);
+ parent::__construct($w, $h, $settings, $fs);
+ }
+
+ /**
+ * Returns an edge markup
+ */
+ protected function getEdge($edge, $x_centre, $y_centre, $depth, $overlay)
+ {
+ list($xo, $yo) = $this->pie_exploder->getExplode($edge->slice['item'],
+ $edge->slice['angle_start'] + $this->s_angle,
+ $edge->slice['angle_end'] + $this->s_angle);
+ return parent::getEdge($edge, $x_centre + $xo, $y_centre + $yo, $depth,
+ $overlay);
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/ExplodedPieGraph.php b/classes/vendor/81x/goat1000/svggraph/ExplodedPieGraph.php
new file mode 100644
index 0000000..fffdc09
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/ExplodedPieGraph.php
@@ -0,0 +1,28 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class ExplodedPieGraph extends PieGraph {
+
+ use ExplodedPieGraphTrait;
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/ExplodedPieGraphTrait.php b/classes/vendor/81x/goat1000/svggraph/ExplodedPieGraphTrait.php
new file mode 100644
index 0000000..b214536
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/ExplodedPieGraphTrait.php
@@ -0,0 +1,117 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+trait ExplodedPieGraphTrait {
+
+ protected $pie_exploder = null;
+ protected $explode_amount = 0;
+
+ /**
+ * Calculates reduced radius of pie
+ */
+ protected function calc()
+ {
+ parent::calc();
+ $this->explode_amount = $this->pie_exploder->fixRadii($this->radius_x,
+ $this->radius_y);
+ }
+
+ /**
+ * Returns a single slice of pie
+ */
+ protected function getSlice($item, $angle_start, $angle_end, $radius_x,
+ $radius_y, &$attr, $single_slice, $colour_index)
+ {
+ if($single_slice)
+ return parent::getSlice($item, $angle_start, $angle_end, $radius_x,
+ $radius_y, $attr, $single_slice, $colour_index);
+
+ // find and apply explosiveness
+ list($xo, $yo) = $this->pie_exploder->getExplode($item, $angle_start +
+ $this->s_angle, $angle_end + $this->s_angle);
+
+ $translated = $attr;
+ $xform = new Transform;
+ $xform->translate($xo, $yo);
+ if(isset($translated['transform']))
+ $translated['transform']->add($xform);
+ else
+ $translated['transform'] = $xform;
+ return parent::getSlice($item, $angle_start, $angle_end, $radius_x,
+ $radius_y, $translated, $single_slice, $colour_index);
+ }
+
+
+ /**
+ * Returns the position for the label
+ */
+ public function dataLabelPosition($dataset, $index, &$item, $x, $y, $w, $h,
+ $label_w, $label_h)
+ {
+ list($pos, $target) = parent::dataLabelPosition($dataset, $index, $item,
+ $x, $y, $w, $h, $label_w, $label_h);
+
+ if(isset($this->slice_info[$index])) {
+ list($xo, $yo) = $this->pie_exploder->getExplode($item,
+ $this->slice_info[$index]->start_angle + $this->s_angle,
+ $this->slice_info[$index]->end_angle + $this->s_angle);
+
+ list($x1, $y1) = explode(' ', $pos);
+ if(is_numeric($x1) && is_numeric($y1)) {
+ $x1 += $xo;
+ $y1 += $yo;
+ } else {
+ $x1 = $xo;
+ $y1 = $yo;
+ }
+
+ // explode target position too
+ $target[0] += $xo;
+ $target[1] += $yo;
+
+ $pos = new Number($x1) . ' ' . new Number($y1);
+ } else {
+ $pos = 'middle centre';
+ }
+ return [$pos, $target];
+ }
+
+ /**
+ * Checks that the data are valid
+ */
+ protected function checkValues()
+ {
+ parent::checkValues();
+
+ $largest = $this->getMaxValue();
+ $smallest = $largest;
+
+ // want smallest non-0 value
+ foreach($this->values[0] as $item)
+ if($item->value < $smallest)
+ $smallest = $item->value;
+
+ $this->pie_exploder = new PieExploder($this, $smallest, $largest);
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/ExplodedSemiDonut3DGraph.php b/classes/vendor/81x/goat1000/svggraph/ExplodedSemiDonut3DGraph.php
new file mode 100644
index 0000000..7b9f6de
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/ExplodedSemiDonut3DGraph.php
@@ -0,0 +1,47 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class ExplodedSemiDonut3DGraph extends SemiDonut3DGraph {
+
+ use ExplodedPieGraphTrait;
+
+ public function __construct($w, $h, array $settings, array $fixed_settings = [])
+ {
+ $fs = [ 'draw_flat_sides' => true, ];
+ $fs = array_merge($fs, $fixed_settings);
+ parent::__construct($w, $h, $settings, $fs);
+ }
+
+ /**
+ * Returns an edge markup
+ */
+ protected function getEdge($edge, $x_centre, $y_centre, $depth, $overlay)
+ {
+ list($xo, $yo) = $this->pie_exploder->getExplode($edge->slice['item'],
+ $edge->slice['angle_start'] + $this->s_angle,
+ $edge->slice['angle_end'] + $this->s_angle);
+ return parent::getEdge($edge, $x_centre + $xo, $y_centre + $yo, $depth,
+ $overlay);
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/ExplodedSemiDonutGraph.php b/classes/vendor/81x/goat1000/svggraph/ExplodedSemiDonutGraph.php
new file mode 100644
index 0000000..7278cd0
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/ExplodedSemiDonutGraph.php
@@ -0,0 +1,48 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class ExplodedSemiDonutGraph extends SemiDonutGraph {
+
+ use ExplodedPieGraphTrait {
+ dataLabelPosition as protected traitDLP;
+ }
+
+ /**
+ * Overridden to keep inner text in the middle
+ */
+ public function dataLabelPosition($dataset, $index, &$item, $x, $y, $w, $h,
+ $label_w, $label_h)
+ {
+ if($dataset === 'innertext') {
+ if($this->getOption('flipped'))
+ $y_offset = new Number($label_h / 2);
+ else
+ $y_offset = new Number($label_h / -2);
+ return ['centre middle 0 ' . $y_offset, [$x, $y] ];
+ }
+
+ return $this->traitDLP($dataset, $index, $item, $x, $y, $w, $h,
+ $label_w, $label_h);
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/FieldSort.php b/classes/vendor/81x/goat1000/svggraph/FieldSort.php
new file mode 100644
index 0000000..8b12bb5
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/FieldSort.php
@@ -0,0 +1,67 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * Class for sorting array by field
+ */
+class FieldSort {
+
+ private $key = null;
+ private $reverse = false;
+
+ public function __construct($key, $reverse = false)
+ {
+ $this->key = $key;
+ $this->reverse = $reverse;
+ }
+
+ /**
+ * Sorts the array based on value of key field
+ */
+ public function sort(&$data)
+ {
+ $key = $this->key;
+ $get_val = function($a, $key) {
+ return (!isset($a[$key]) || $a[$key] === null ? PHP_INT_MIN : $a[$key]);
+ };
+ $bigger = function($a, $b, $key) use($get_val) {
+ $va = $get_val($a, $key);
+ $vb = $get_val($b, $key);
+ if($va == $vb)
+ return 0;
+ return $va > $vb ? 1 : -1;
+ };
+ $smaller = function($a, $b, $key) use($get_val) {
+ $va = $get_val($a, $key);
+ $vb = $get_val($b, $key);
+ if($va == $vb)
+ return 0;
+ return $va < $vb ? 1 : -1;
+ };
+ $fn = $this->reverse ? $smaller : $bigger;
+ usort($data, function($a, $b) use($key, $fn) {
+ return $fn($a, $b, $key);
+ });
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/FigureShape.php b/classes/vendor/81x/goat1000/svggraph/FigureShape.php
new file mode 100644
index 0000000..06df5a5
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/FigureShape.php
@@ -0,0 +1,37 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class FigureShape extends MarkerShape {
+ protected $required = ['name', 'x', 'y'];
+
+ /**
+ * Override to draw a marker
+ */
+ protected function drawElement(&$graph, &$attributes)
+ {
+ $attributes['type'] = 'figure:' . $attributes['name'];
+ unset($attributes['name']);
+ return parent::drawElement($graph, $attributes);
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/Figures.php b/classes/vendor/81x/goat1000/svggraph/Figures.php
new file mode 100644
index 0000000..815b2ea
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/Figures.php
@@ -0,0 +1,96 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class Figures {
+
+ private $graph;
+ private $settings;
+ private $loaded = false;
+ private $figure_map = [];
+
+ public function __construct(&$graph, &$settings)
+ {
+ $this->graph =& $graph;
+ $this->settings =& $settings;
+ }
+
+ /**
+ * Load figures from options list
+ */
+ public function load()
+ {
+ if($this->loaded)
+ return;
+ $this->loaded = true;
+
+ if(!isset($this->settings['figure']))
+ return;
+
+ if(!is_array($this->settings['figure']) ||
+ !isset($this->settings['figure'][0]))
+ throw new \Exception('Malformed figure option.');
+
+ if(!is_array($this->settings['figure'][0])) {
+ $this->addFigure($this->settings['figure']);
+ } else {
+ foreach($this->settings['figure'] as $figure) {
+ $this->addFigure($figure);
+ }
+ }
+ }
+
+ /**
+ * Adds a figure to the list
+ */
+ private function addFigure($figure_array)
+ {
+ $name = array_shift($figure_array);
+ if(isset($this->figure_map[$name]))
+ throw new \Exception('Figure [' . $name . '] defined more than once.');
+ $content = '';
+ $shapes = $this->graph->getShapeList();
+ if(!is_array($figure_array[0])) {
+ $shape = $shapes->getShape($figure_array);
+ $content .= $shape->draw($this->graph);
+ } else {
+ foreach($figure_array as $s) {
+ $shape = $shapes->getShape($s);
+ $content .= $shape->draw($this->graph);
+ }
+ }
+ $id = $this->graph->defs->defineSymbol($content);
+ $this->figure_map[$name] = $id;
+ }
+
+ /**
+ * Returns a figure's symbol ID by name
+ */
+ public function getFigure($name)
+ {
+ $this->load();
+ if(isset($this->figure_map[$name]))
+ return $this->figure_map[$name];
+ return null;
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/FilterList.php b/classes/vendor/81x/goat1000/svggraph/FilterList.php
new file mode 100644
index 0000000..8f3199d
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/FilterList.php
@@ -0,0 +1,126 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class FilterList {
+ private $graph;
+ private $filters = [];
+
+ public function __construct(&$graph)
+ {
+ $this->graph =& $graph;
+ }
+
+ /**
+ * Creates a shadow
+ */
+ public function shadow($params)
+ {
+ $opts = [
+ 'offset_x' => 10, 'offset_y' => 10,
+ 'opacity' => 0.5, 'blur' => 3,
+ 'shadow_only' => false,
+ ];
+ if(is_array($params))
+ $opts = array_merge($opts, $params);
+
+ // matrix converts to black and sets opacity
+ $o = new Number(min(max($opts['opacity'], 0.005), 1.0));
+ $matrix = [
+ 'type' => 'matrix',
+ 'values' =>
+ '0 0 0 0 0 ' .
+ '0 0 0 0 0 ' .
+ '0 0 0 0 0 ' .
+ '0 0 0 ' . $o . ' 0',
+ ];
+ $matrix = $this->graph->element('feColorMatrix', $matrix);
+ $offset = $blur = '';
+
+ // offset positions the shadow
+ $offsets = [
+ 'dx' => new Number($opts['offset_x']),
+ 'dy' => new Number($opts['offset_y']),
+ 'result' => 'res',
+ ];
+ if($offsets['dx']->value || $offsets['dy']->value)
+ $offset = $this->graph->element('feOffset', $offsets);
+
+ // blur the outline
+ $gblur = [
+ 'stdDeviation' => new Number($opts['blur']),
+ 'result' => 'res',
+ ];
+ if($gblur['stdDeviation']->value > 0)
+ $blur = $this->graph->element('feGaussianBlur', $gblur);
+
+ // if there is no blur and no offset, there is no shadow
+ if($blur === '' && $offset === '')
+ return null;
+
+ $content = $matrix . $offset . $blur;
+ if(!$opts['shadow_only']) {
+ $merged = $this->graph->element('feMergeNode', ['in' => 'res']) .
+ $this->graph->element('feMergeNode', ['in' => 'SourceGraphic']);
+ $content .= $this->graph->element('feMerge', null, null, $merged);
+ }
+
+ $filter = [
+ 'id' => $this->graph->newID(),
+ 'filterUnits' => 'userSpaceOnUse'
+ ];
+
+ return [
+ 'id' => $filter['id'],
+ 'content' => $this->graph->element('filter', $filter, null, $content),
+ ];
+ }
+
+ /**
+ * Adds a filter
+ */
+ public function add($type, $params = null)
+ {
+ $key = md5(serialize([$type, $params]));
+ if(isset($this->filters[$key]))
+ return $this->filters[$key]['id'];
+
+ if(!method_exists($this, $type))
+ throw new \InvalidArgumentException('Unknown filter: ' . $type);
+
+ $result = $this->{$type}($params);
+ if($result === null)
+ return null;
+ $this->filters[$key] = $result;
+ return $result['id'];
+ }
+
+ /**
+ * Adds the filters to the defs
+ */
+ public function makeFilters(&$defs)
+ {
+ foreach($this->filters as $filter)
+ $defs->add($filter['content']);
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/FloatingBarGraph.php b/classes/vendor/81x/goat1000/svggraph/FloatingBarGraph.php
new file mode 100644
index 0000000..0774c27
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/FloatingBarGraph.php
@@ -0,0 +1,35 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class FloatingBarGraph extends BarGraph {
+
+ use FloatingBarTrait;
+
+ public function __construct($w, $h, array $settings, array $fixed_settings = [])
+ {
+ $fs = ['require_structured' => ['end']];
+ $fs = array_merge($fs, $fixed_settings);
+ parent::__construct($w, $h, $settings, $fs);
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/FloatingBarTrait.php b/classes/vendor/81x/goat1000/svggraph/FloatingBarTrait.php
new file mode 100644
index 0000000..49602d9
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/FloatingBarTrait.php
@@ -0,0 +1,108 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+trait FloatingBarTrait {
+
+ private $min_value = null;
+ private $max_value = null;
+
+ /**
+ * Returns an array with x, y, width and height set
+ */
+ protected function barDimensions($item, $index, $start, $axis, $dataset)
+ {
+ $bar = [];
+ $bar_x = $this->barX($item, $index, $bar, $axis, $dataset);
+ if($bar_x === null)
+ return [];
+
+ $start = $item->value;
+ $value = $item->end - $start;
+ $y_pos = $this->barY($value, $bar, $start, $axis);
+ if($y_pos === null)
+ return [];
+ return $bar;
+ }
+
+ /**
+ * Override to replace value
+ */
+ protected function setTooltip(&$element, &$item, $dataset, $key, $value = null,
+ $duplicate = false)
+ {
+ $value = $item->end - $item->value;
+ return parent::setTooltip($element, $item, $dataset, $key, $value, $duplicate);
+ }
+
+ /**
+ * Returns the maximum bar end
+ */
+ public function getMaxValue()
+ {
+ if($this->max_value !== null)
+ return $this->max_value;
+ $max = null;
+ foreach($this->values[0] as $item) {
+ $s = $item->value;
+ $e = $item->end;
+ if($s === null || $e === null)
+ continue;
+ $m = max($s, $e);
+ if($max === null || $m > $max)
+ $max = $m;
+ }
+ return ($this->max_value = $max);
+ }
+
+ /**
+ * Returns the minimum bar end
+ */
+ public function getMinValue()
+ {
+ if($this->min_value !== null)
+ return $this->min_value;
+ $min = null;
+ foreach($this->values[0] as $item) {
+ $s = $item->value;
+ $e = $item->end;
+ if($s === null || $e === null)
+ continue;
+ $m = min($s, $e);
+ if($min === null || $m < $min)
+ $min = $m;
+ }
+ return ($this->min_value = $min);
+ }
+
+ /**
+ * Returns TRUE if the item is visible on the graph
+ */
+ public function isVisible($item, $dataset = 0)
+ {
+ if($item->value === null || $item->end === null)
+ return false;
+ return ($item->end - $item->value != 0);
+ }
+
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/GanttArrow.php b/classes/vendor/81x/goat1000/svggraph/GanttArrow.php
new file mode 100644
index 0000000..7769768
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/GanttArrow.php
@@ -0,0 +1,93 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * A class for drawing arrows
+ */
+class GanttArrow extends Arrow {
+
+ protected $type = 0;
+ protected $vsplit = false;
+ protected $space = 10;
+
+ public function __construct(Point $a, Point $b, $wa, $ha, $wb, $hb, $type, $sp)
+ {
+ switch($type) {
+
+ case 'SS':
+ $this->vsplit = ($a->x < $b->x);
+ break;
+
+ case 'FF':
+ $a->x += $wa;
+ $b->x += $wb;
+ $this->vsplit = ($a->x > $b->x);
+ break;
+
+ case 'SF':
+ $b->x += $wb;
+ $this->vsplit = ($a->x < $b->x);
+ break;
+
+ case 'FS':
+ default:
+ $a->x += $wa;
+ $this->vsplit = ($a->x > $b->x);
+ }
+ $a->y += $ha;
+
+ // only start horizontal if the first element has some width
+ if($wa < 1)
+ $this->vsplit = true;
+
+ parent::__construct($a, $b);
+ $this->type = $type;
+ $this->space = max(5, $sp);
+ }
+
+ /**
+ * Returns the PathData for an arrow line
+ */
+ protected function getArrowPath()
+ {
+ $p = new PathData('M', $this->a);
+ $dx = $this->b->x - $this->a->x;
+ $dy = $this->b->y - $this->a->y;
+
+ if($dx && $this->vsplit) {
+ $v1 = new Number($dy - $this->space);
+ $v2 = new Number($this->space);
+ $p->add('v', $v1);
+ $p->add('h', new Number($dx));
+ $p->add('v', $v2);
+
+ } else {
+ // if horizontal very small, ignore it
+ if(abs($dx) > 0.1)
+ $p->add('h', new Number($dx));
+ $p->add('v', new Number($this->b->y - $this->a->y));
+ }
+ return $p;
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/GanttChart.php b/classes/vendor/81x/goat1000/svggraph/GanttChart.php
new file mode 100644
index 0000000..8d5837e
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/GanttChart.php
@@ -0,0 +1,1098 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class GanttChart extends HorizontalBarGraph {
+
+ protected $start_date = null;
+ protected $end_date = null;
+ protected $auto_format = true;
+ protected $bar_list = [];
+ protected $enabled_datasets = [];
+
+ public function __construct($w, $h, array $settings, array $fixed_settings = [])
+ {
+ // if the format for the date/time axis is not set, figure one out
+ $this->auto_format = !isset($settings['datetime_text_format']);
+
+ $fs = ['require_structured' => ['end'], ];
+ $fs = array_merge($fs, $fixed_settings);
+ parent::__construct($w, $h, $settings, $fs);
+ }
+
+ /**
+ * Converts dates early
+ */
+ public function values($values)
+ {
+ $res = parent::values($values);
+ if(empty($values) || $this->values->error)
+ return $res;
+
+ // find list of enabled datasets
+ $d_count = count($this->values);
+ $d_enabled = $this->getOption("dataset");
+ if($d_enabled === null) {
+ $d_enabled = range(0, $d_count - 1);
+ } else {
+ $enabled = [];
+ if(!is_array($d_enabled))
+ $d_enabled = [$d_enabled];
+ $d_enabled = array_unique($d_enabled);
+ foreach($d_enabled as $d) {
+ if($d > 0 && $d < $d_count)
+ $enabled[] = $d;
+ }
+ $d_enabled = $enabled;
+ }
+ $this->enabled_datasets = $d_enabled;
+
+ // set up class for adjusting times
+ $units = $this->getOption('gantt_units');
+ $ts = new TimeSpanner($units);
+
+ // convert times to seconds, find start and end
+ $start_date = $end_date = null;
+ $update_times = function($item) use (&$start_date, &$end_date, $ts) {
+ if(!isset($item->value))
+ return;
+ $s = Graph::dateConvert($item->value);
+ if($s !== null) {
+ $s = $ts->start($s);
+ if($start_date === null || $start_date > $s)
+ $start_date = $s;
+ $item->value = $s;
+
+ $e = isset($item->end) ? Graph::dateConvert($item->end) : null;
+ if($e === null) {
+ $e = $s;
+ } else {
+ $e = $ts->end($e);
+ }
+
+ if($end_date === null || $end_date < $e)
+ $end_date = $e;
+ $item->end = $e;
+ }
+ return $item;
+ };
+ foreach($d_enabled as $dataset)
+ $this->values->transform($update_times, $dataset);
+
+ // find groups
+ $groups = [];
+ $item_groups = [];
+ $key = null;
+ $entries = 0;
+ $levels = [];
+
+ foreach($d_enabled as $dataset) {
+ foreach($this->values[$dataset] as $item) {
+
+ if($item->group !== null) {
+
+ // things get strange if groups are in later datasets
+ if($dataset > 0 && !isset($groups[$item->key]))
+ throw new \Exception('Groups must be in dataset 0');
+
+ // numeric group has max number of entries
+ $entries = is_numeric($item->group) ? (int)$item->group : 1e6;
+ $key = $item->key;
+ if(!isset($groups[$key])) {
+ $groups[$key] = [
+ 'start' => 0,
+ 'end' => 0,
+ 'total_time' => 0,
+ 'total_complete' => 0,
+ 'level' => 0,
+ ];
+ }
+
+ // named groups for multiple levels
+ $group_name = is_string($item->group) ? $item->group : 'unnamed_group';
+ if(isset($levels[$group_name])) {
+ $new_levels = [];
+ foreach($levels as $k => $v) {
+ if($k == $group_name)
+ break;
+ $new_levels[$k] = $v;
+ }
+ $levels = $new_levels;
+ }
+
+ $levels[$group_name] = $key;
+ $groups[$key]['level'] = count($levels);
+ continue;
+ }
+
+ // not a group
+ if($key !== null && $entries) {
+ if($item->value === null)
+ continue;
+
+ // groups and tasks/milestones don't work together on a row
+ if(isset($groups[$item->key]))
+ throw new \Exception('Groups must not be mixed with tasks/milestones');
+
+ $item_groups[$item->key] = $key;
+ $item_time = $item->end - $item->value;
+ $item_percent = $item_time > 0 && is_numeric($item->complete) ?
+ $item_time * $item->complete / 100 : 0;
+
+ // update group hierarchy
+ foreach($levels as $level => $key) {
+ $g = &$groups[$key];
+ if($g['start'] == 0 || $item->value < $g['start'])
+ $g['start'] = $item->value;
+ if($g['end'] == 0 || $item->value > $g['end'])
+ $g['end'] = $item->value;
+ if($g['end'] == 0 || $item->end > $g['end'])
+ $g['end'] = $item->end;
+ if($item_time > 0) {
+ $g['total_time'] += $item_time;
+ $g['total_complete'] += $item_percent;
+ }
+ }
+ --$entries;
+ }
+ }
+ }
+
+ // update groups with real dates, percentages, text classes
+ $this->values->addField('axis_text_class');
+
+ $fix_groups = function($item) use ($groups, $item_groups) {
+ if(!isset($groups[$item->key])) {
+ if(isset($item->axis_text_class))
+ return null;
+
+ $level = 0;
+ if(isset($item_groups[$item->key]))
+ $level = $groups[$item_groups[$item->key]]['level'];
+ $item->axis_text_class = isset($item->milestone) ?
+ 'gantt_milestone:' . $level :
+ 'gantt_item:' . $level;
+ return $item;
+ }
+
+ $g =& $groups[$item->key];
+ $item->value = $g['start'];
+ $item->end = $g['end'];
+ if($g['total_time'] && $g['total_complete']) {
+ $item->complete = 100 * $g['total_complete'] / $g['total_time'];
+ }
+ $item->axis_text_class = 'gantt_group:' . $g['level'];
+ return $item;
+ };
+ $this->values->transform($fix_groups);
+
+ // copy found dates to class
+ $this->start_date = $start_date;
+ $this->end_date = $end_date;
+ }
+
+ /**
+ * Sets up the colours used for the graph, and other things
+ */
+ protected function setup()
+ {
+ $dataset = $this->getOption(['dataset', 0], 0);
+ $icount = $this->values->itemsCount($dataset);
+
+ // axis min/max alter number of items
+ $max = $this->getOption(['axis_max_v', 0], 1e7) + 1;
+ if($max < $icount)
+ $icount = $max;
+ $min = $this->getOption(['axis_min_v', 0], 0);
+ if($min > 0)
+ $icount -= $min;
+ if($icount < 1)
+ throw new \Exception('No items to display');
+
+ // use two datasets for main/completed colour
+ $this->colourSetup($icount, 2);
+ if(!is_numeric($this->height))
+ $this->autoHeight($icount);
+ }
+
+ /**
+ * Setup code here for before drawing starts but after axes set
+ */
+ protected function barSetup()
+ {
+ parent::barSetup();
+
+ $shapes = $this->getDayShading();
+ if($this->getOption('gantt_today')) {
+ $ds = $this->getToday();
+ if(!empty($ds))
+ $shapes = array_merge($shapes, $ds);
+ }
+
+ if(!empty($shapes)) {
+ $o_shapes = $this->getOption('shape');
+ if(is_array($o_shapes)) {
+ if(is_array($o_shapes[0]))
+ $shapes = array_merge($shapes, $o_shapes);
+ else
+ $shapes[] = $o_shapes;
+ }
+ $this->setOption('shape', $shapes);
+ }
+ }
+
+ /**
+ * Override BarGraphTrait::drawBars to draw multiple datasets
+ */
+ protected function drawBars()
+ {
+ $this->barSetup();
+ $bars = '';
+
+ // use a MultiGraph to traverse more easily
+ $multi_graph = new MultiGraph($this->values, false, false, false);
+ foreach($multi_graph as $bnum => $itemlist) {
+ foreach($this->enabled_datasets as $dataset) {
+ $item = $itemlist[$dataset];
+ $this->setBarLegendEntry($dataset, $bnum, $item);
+ $bars .= $this->drawBar($item, $bnum, 0, null, $dataset);
+ }
+ }
+
+ return $bars;
+ }
+
+ /**
+ * Calculates a height for the graph
+ */
+ private function autoHeight($items)
+ {
+ $axis = null;
+ $v_d_a = new DisplayAxis($this, $axis, 0, 'v', 'x', true, false);
+ $h_d_a = new DisplayAxis($this, $axis, 0, 'h', 'x', true, false);
+ $v_style = $v_d_a->getStyling();
+ $h_style = $h_d_a->getStyling();
+
+ // fixed space allows for two lines of labels, plus two of headings, plus padding
+ $fixed = $this->pad_bottom + $this->pad_top;
+ $fixed += $this->getOption('axis_pad_top') + $this->getOption('axis_pad_bottom');
+
+ if(isset($h_style['t_font_size'])) {
+ $space = isset($h_style['t_space']) ? $h_style['t_space'] : 1;
+ if($h_style['d_style'] == 'box')
+ $space *= 2;
+ $text_size = ($space + $h_style['t_font_size']) * 2;
+ if($this->getOption('axis_double_x'))
+ $text_size *= 2;
+ $fixed += $text_size;
+ }
+ if(isset($h_style['l_font_size'])) {
+ $space = isset($h_style['l_space']) ? $h_style['l_space'] : 1;
+ $fixed += ($space + $h_style['l_font_size']) * 2;
+ }
+
+ // add in space for any titles
+ $titles = $this->getTitle();
+ if($titles['font_size'] && ($titles['pos'] == 'top' || $titles['pos'] == 'bottom')) {
+ $fixed += $titles['height'] + $titles['space'];
+ if($titles['sfont_size'])
+ $fixed += $titles['sheight'] + $titles['sspace'];
+ }
+
+ $min_height = 10;
+ $ch = $this->getOption('gantt_group_corner_width') ?
+ $this->getOption('gantt_group_corner_height') : 0;
+ $bw = max($this->getOption('bar_width'), 2);
+ $bs = max($this->getOption('bar_space'), 2);
+ $bar = max($ch, $bs) + $bw;
+ $marker = max($this->getOption('gantt_milestone_size'), 2) * 2;
+ $font_size = $min_height;
+ if(isset($v_style['t_font_size']))
+ $font_size = $v_style['t_font_size'];
+
+ // get largest font size from text classes
+ $classes = ['gantt_group:', 'gantt_item:', 'gantt_milestone:'];
+ for($i = 0; $i < 20; ++$i) {
+ foreach($classes as $cls) {
+ $tc = new TextClass($cls . $i);
+ $sz = $tc->font_size;
+ if($sz > $font_size)
+ $font_size = $sz;
+ }
+ }
+ $text = $font_size * 1.5;
+
+ // each row is the biggest of bar, milestone, text label, or fallback value
+ $row_height = max($min_height, $bar, $marker, $text);
+ $this->height = $fixed + $items * $row_height;
+ }
+
+ /**
+ * Choose a format for the axis depending on the length in time and pixels
+ */
+ private function autoFormatAxis($ends)
+ {
+ // (roughly) measure the horizontal space taken by Y-axis
+ $min_space = 1;
+ $grid_division = 1;
+ $length = $this->height - $this->pad_top - $this->pad_bottom;
+ $l_c = $this->getOption('label_centre');
+ $factory = $this->getYAxisFactory();
+ $axis = $this->createYAxis($factory, $length, $ends, 0, $min_space, $grid_division);
+ $display_axis = new DisplayAxis($this, $axis, 0, 'v', 'x', true, $l_c);
+ $bbox = $display_axis->measure();
+ $left_text = $bbox->x2 - $bbox->x1;
+ if($this->getOption('axis_double_y'))
+ $left_text *= 2;
+
+ // approximate length of X-axis
+ $length = $this->width - $this->pad_left - $this->pad_right - $left_text;
+ $min_space = $this->getOption(['minimum_grid_spacing_h', 0], 'minimum_grid_spacing');
+ $want_space = 5; // amount of space wanted between labels
+ $good_fit = false;
+ $divisions = [
+ ['100 year', 'Y'], ['50 year', 'Y'], ['20 year', 'Y'], ['10 year', 'Y\'\s'],
+
+ ['1 year', 'Y'],
+ ['6 month', ['M', 'Y']],
+ ['3 month', ['M', 'Y']],
+ ['2 month', ['M', 'Y']],
+ ['1 month', ['M', 'Y']],
+ ['14 day', ['d M', 'Y']],
+ ['7 day', ['d M', 'Y']],
+ ['1 day', ['d', 'M Y']],
+ ['1 day', ['D d', 'M Y']],
+ ];
+
+ // if using units smaller than days might need smaller divisions
+ $units = $this->getOption('gantt_units');
+ if($units == 'hour' || $units == 'minute') {
+ $more_divisions = [
+ ['12 hour', ['H:i', 'D d M Y']],
+ ['6 hour', ['H:i', 'D d M Y']],
+ ['3 hour', ['H:i', 'D d M Y']],
+ ['2 hour', ['H:i', 'D d M Y']],
+ ['1 hour', ['H:i', 'D d M Y']],
+ ];
+ $divisions = array_merge($divisions, $more_divisions);
+ }
+ $subdivisions = [
+ '1 hour' => '30 minute',
+ '2 hour' => '1 hour',
+ '3 hour' => '1 hour',
+ '6 hour' => '1 hour',
+ '12 hour' => '2 hour',
+ '1 day' => '6 hour',
+ ];
+ $div_id = count($divisions) - 1;
+
+ $factory = $this->getXAxisFactory();
+ $fmt = $div = null;
+ while(!$good_fit) {
+
+ // find out how well the division fits
+ list($div, $fmt) = $divisions[$div_id];
+ $levels = is_array($fmt) ? count($fmt) : 1;
+ $this->setOption('datetime_text_format', $fmt);
+ $this->setOption('axis_levels_h', $levels);
+
+ $axis = $this->createXAxis($factory, $length, $ends, 0, $min_space, $div);
+ if($levels > 1)
+ $display_axis = new DisplayAxisLevels($this, $axis, 0, 'h', 'x', true, $l_c);
+ else
+ $display_axis = new DisplayAxis($this, $axis, 0, 'h', 'x', true, $l_c);
+
+ $overlap = $display_axis->getTextOverlap();
+ if($overlap < -$want_space)
+ $good_fit = true;
+
+ // give up?
+ if($overlap === null || --$div_id < 0) {
+ $this->setOption('datetime_text_format', null);
+ $this->setOption('axis_levels_h', 1);
+ return;
+ }
+ }
+
+ if($fmt !== null) {
+ $this->setOption('datetime_text_format', $fmt);
+ $this->setOption('axis_levels_h', $levels);
+ }
+ if($div !== null) {
+ $this->setOption('grid_division_h', $div);
+ if(isset($subdivisions[$div]))
+ $this->setOption('subdivision_h', $subdivisions[$div]);
+ }
+ $this->auto_format = false;
+ }
+
+ /**
+ * Sets up the shading for weekends (or whatever)
+ */
+ private function getDayShading()
+ {
+ $shade_days = $this->getOption('gantt_shade_days');
+ if(!is_array($shade_days) || empty($shade_days))
+ return [];
+
+ // get the values at the axis ends from the axis
+ $axis = $this->getAxis('x', null);
+ $a_len = $axis->getLength();
+ $date = $axis->value(0);
+ $end_time = $axis->value($a_len);
+
+ // check how long a day is on this axis
+ $timescale = $end_time - $date;
+ $days = $timescale / 86400;
+ $day_pixels = $a_len / $days;
+ if($day_pixels < 1)
+ return [];
+
+ // set up a rect or NULL for each day of the week
+ $per_day = [];
+ for($i = 0; $i < 7; ++$i) {
+ if(in_array($i, $shade_days)) {
+ $per_day[$i] = [
+ 'rect', 'x' => 'gl', 'y' => 'gt',
+ 'width' => 'u1 days', 'height' => 'gh',
+ 'fill' => $this->getOption(['gantt_shade_days_colour', $i]),
+ 'opacity' => $this->getOption(['gantt_shade_days_opacity', $i]),
+ 'stroke' => 'none',
+ ];
+ } else {
+ $per_day[$i] = null;
+ }
+ }
+
+ // make an array of rects for shading days
+ $shapes = [];
+ $dd = new \DateTime('@' . new Number($date));
+ $dw = $dd->format('w');
+ while($date < $end_time) {
+ if($per_day[$dw] !== null) {
+ $rect = $per_day[$dw];
+ $dd = new \DateTime('@' . new Number($date));
+ $rect['x'] = 'g' . $dd->format('Y-m-d');
+ $shapes[] = $rect;
+ }
+ $dw = ($dw + 1) % 7;
+ $date += 86400;
+ }
+ return $shapes;
+ }
+
+ /**
+ * Returns the shape that marks the current day
+ */
+ protected function getToday()
+ {
+ $today = $t_i = null;
+ $when = $this->getOption('gantt_today_date');
+ if($when) {
+ $t_i = Graph::dateConvert($when);
+ if($t_i !== null)
+ $today = new \DateTime('@' . $t_i);
+ }
+ if($today === null) {
+ $today = new \DateTime();
+ $t_i = $today->format('U');
+ }
+
+ // check that today is on the chart
+ $axis = $this->getAxis('x', null);
+ $a_len = $axis->getLength();
+ $start_time = $axis->value(0);
+ $end_time = $axis->value($a_len);
+
+ if($t_i < $start_time || $t_i > $end_time)
+ return null;
+
+ $stroke_width = min(10, max(0.1, $this->getOption('gantt_today_width')));
+ $dash = $this->getOption('gantt_today_dash');
+ $opacity = min(1, max(0, $this->getOption('gantt_today_opacity')));
+ if($opacity == 0)
+ return null;
+
+ $midday = 'g' . $today->format('Y-m-d') . 'T12:00:00';
+ $shape = [
+ 'line',
+ 'x1' => $midday, 'x2' => $midday,
+ 'y1' => 'gt', 'y2' => 'gb',
+ ];
+ $shape['stroke'] = $this->getOption('gantt_today_colour');
+ if($stroke_width != 1)
+ $shape['stroke-width'] = $stroke_width;
+ if(!empty($dash))
+ $shape['stroke-dasharray'] = $dash;
+ if($opacity < 1)
+ $shape['opacity'] = $opacity;
+ return [$shape];
+ }
+
+ /**
+ * Returns fixed min and max option for an axis
+ */
+ protected function getFixedAxisOptions($axis, $index)
+ {
+ $a = $axis == 'y' ? 'h' : 'v';
+ $min = $this->getOption(['axis_min_' . $a, $index]);
+ $max = $this->getOption(['axis_max_' . $a, $index]);
+ if($axis == 'y') {
+ if($min !== null) {
+ $min = Graph::dateConvert($min);
+ } else {
+
+ // need to set a minimum, or it will end up as 1970
+ $min = $this->start_date;
+ }
+ if($max !== null)
+ $max = Graph::dateConvert($max);
+ }
+ return [$min, $max];
+ }
+
+ /**
+ * Min value is the stored start date
+ */
+ public function getMinValue()
+ {
+ return $this->start_date;
+ }
+
+ /**
+ * Max value is the stored end date
+ */
+ public function getMaxValue()
+ {
+ return $this->end_date;
+ }
+
+ /**
+ * Both axes are X-type for Gantt chart
+ */
+ protected function getDisplayAxis($axis, $axis_no, $orientation, $type)
+ {
+ $var = 'main_' . $type . '_axis';
+ $main = ($axis_no == $this->{$var});
+ $levels = $this->getOption(['axis_levels_' . $orientation, $axis_no]);
+ $class = 'Goat1000\SVGGraph\DisplayAxis';
+ if(is_numeric($levels) && $levels > 1)
+ $class = 'Goat1000\SVGGraph\DisplayAxisLevels';
+
+ return new $class($this, $axis, $axis_no, $orientation, 'x', $main,
+ $this->getOption('label_centre'));
+ }
+
+ /**
+ * Override to pre-calculate axis settings
+ */
+ protected function getAxisEnds()
+ {
+ // now is the time to figure out the best format
+ if($this->auto_format) {
+ $ends = parent::getAxisEnds();
+ $this->autoFormatAxis($ends);
+ }
+
+ return parent::getAxisEnds();
+ }
+
+ /**
+ * Override to always return datetime axis
+ */
+ protected function getXAxisFactory()
+ {
+ return new AxisFactory(true, $this->settings, false, false, false);
+ }
+
+ /**
+ * Override to always want bar-style Y axis
+ */
+ protected function getYAxisFactory()
+ {
+ // don't reverse the vertical axis for Gantt charts
+ return new AxisFactory($this->getOption('datetime_keys'), $this->settings,
+ true, true, false);
+ }
+
+ /**
+ * Returns an array with x, y, width and height set
+ */
+ protected function barDimensions($item, $index, $start, $axis, $dataset)
+ {
+ $bar = [];
+ $bar_x = $this->barX($item, $index, $bar, $axis, $dataset);
+ if($bar_x === null)
+ return [];
+
+ $start = $item->value;
+ $value = $item->milestone ? 0 : $item->end - $start;
+
+ // if this is not a milestone, ignore backwards bars
+ if($value < 0)
+ return [];
+
+ $y_pos = $this->barY($value, $bar, $start, $axis);
+ if($y_pos === null)
+ return [];
+ return $bar;
+ }
+
+ /**
+ * Returns the SVG code for a bar or milestone
+ */
+ protected function drawBar(DataItem $item, $index, $start = 0, $axis = null,
+ $dataset = 0, $options = [])
+ {
+ if($item->value === null)
+ return '';
+
+ $bar = $this->barDimensions($item, $index, $start, $axis, $dataset);
+ if(empty($bar))
+ return '';
+
+ // check if this item is off the sides
+ $element = $this->getPointer($item, $index, $dataset, $bar);
+ if($element) {
+ $m = new MarkerShape($element, 'above');
+ return $m->draw($this);
+ }
+
+ if($item->milestone) {
+ if($this->gridX($item->value) === null)
+ return null;
+ $element = $this->getMilestone($item, $index, $dataset, $bar);
+ $label = $item->axis_text ? $item->axis_text : $item->key;
+ $label_shown = $this->addDataLabel($dataset, $index, $element, $item,
+ $bar['x'], $bar['y'], $element['size'], $bar['height'], $label);
+ } else {
+ $element = $this->getBar($item, $index, $dataset, $bar);
+ $bar_type = $element['element'];
+ $bar_content = $element['content'];
+ unset($element['element'], $element['content']);
+
+ // data label is % completion
+ $complete = new Number($item->complete ? $item->complete : 0);
+ $label = "[{$complete}%]";
+ $label_shown = $this->addDataLabel($dataset, $index, $element, $item,
+ $bar['x'], $bar['y'], $bar['width'], $bar['height'], $label);
+ }
+
+ $depends = $this->drawDependencies($item, $index, $dataset, $bar);
+
+ if($this->getOption('semantic_classes'))
+ $element['class'] = 'series' . $dataset;
+
+ if($this->getOption('show_tooltips'))
+ $this->setTooltip($element, $item, $dataset, $item->key, $item->value,
+ $label_shown);
+ if($this->getOption('show_context_menu'))
+ $this->setContextMenu($element, $dataset, $item, $label_shown);
+
+ $task_entry = '';
+ if($item->milestone) {
+ $m = new MarkerShape($element, 'above');
+ $task_entry .= $m->draw($this);
+ } else {
+ $bar_part = $this->element($bar_type, $element, null, $bar_content);
+ $task_entry .= $this->getLink($item, $item->key, $bar_part);
+ }
+ return $task_entry . $depends;
+ }
+
+ /**
+ * Returns the incomplete and complete colours for a bar/group
+ */
+ protected function getBarColours(DataItem $item, $index, $dataset)
+ {
+ // use datasets 0 and 1 for incomplete and complete colours
+ $colour_incomplete = $this->getColour($item, $index, 0);
+ $colour_complete = $this->getColour($item, $index, 1);
+
+ if($item->group) {
+ // group bars are coloured differently
+ $ci = $this->getItemOption('gantt_group_colour', $dataset, $item, 'colour');
+ $cc = $this->getItemOption('gantt_group_colour_complete', $dataset, $item, 'colour_complete');
+ if(!empty($ci)) {
+ $cg = new ColourGroup($this, $item, $index, 0, 'gantt_group_colour', null, 'colour');
+ $colour_incomplete = $cg->stroke();
+ }
+ if(!empty($cc)) {
+ $cg = new ColourGroup($this, $item, $index, 1, 'gantt_group_colour_complete', null, 'colour_complete');
+ $colour_complete = $cg->stroke();
+ }
+ } else {
+ // support fill/fillColour for individual bar complete colours
+ $cc = $this->getItemOption('colour_complete', 0, $item);
+ if(!empty($cc)) {
+ $cg = new ColourGroup($this, $item, $index, 1, 'colour_complete', null, 'colour_complete');
+ $colour_complete = $cg->stroke();
+ }
+ }
+ return [$colour_incomplete, $colour_complete];
+ }
+
+ /**
+ * Returns the attributes of a bar
+ */
+ protected function getBar(DataItem $item, $index, $dataset, &$bar)
+ {
+ list($colour_incomplete, $colour_complete) = $this->getBarColours($item, $index, $dataset);
+ $round = max($this->getItemOption('bar_round', $dataset, $item), 0);
+ $corner_width = $corner_height = 0;
+ if($round > 0) {
+ // don't allow the round corner to be more than 1/2 bar width or height
+ $bar['rx'] = $bar['ry'] = min($round, $bar['width'] / 2, $bar['height'] / 2);
+ }
+
+ if($item->group) {
+ $corner_width = $this->getItemOption('gantt_group_corner_width', $dataset, $item, 'corner_width');
+ $corner_height = $this->getItemOption('gantt_group_corner_height', $dataset, $item, 'corner_height');
+ }
+
+ $element = $bar;
+
+ // group bar has downward pointing corners
+ if($corner_height && $corner_width) {
+
+ // make sure the corners are not bigger than the whole bar
+ $corner_width = max(0.5, min($corner_width, ($bar['width'] - 2) / 2));
+
+ $path = ['element' => 'path', 'content' => null];
+ $inner = $bar['width'] - $corner_width * 2;
+ $p = new PathData('M', $bar['x'], $bar['y'] - $corner_height / 2);
+ $p->add('h', $bar['width']);
+ $p->add('v', $bar['height'] + $corner_height);
+ $p->add('l', -$corner_width, -$corner_height);
+ $p->add('h', -$inner);
+ $p->add('l', -$corner_width, $corner_height);
+ $p->add('z');
+ $path['d'] = $p;
+ $element = $path;
+
+ // update $bar
+ $bar['y'] -= $corner_height / 2;
+ $bar['height'] += $corner_height;
+ } else {
+
+ $element['element'] = 'rect';
+ $element['content'] = null;
+ }
+ if($item->complete >= 100) {
+ $element['fill'] = $colour_complete;
+ $this->setStroke($element, $item, $index, 0);
+ return $element;
+ }
+
+ if($item->complete <= 0) {
+ $element['fill'] = $colour_incomplete;
+ $this->setStroke($element, $item, $index, 0);
+ return $element;
+ }
+
+ $type = $element['element'];
+ unset($element['element'], $element['content']);
+
+ // % complete
+ $c = $this->getClippers($bar['x'], $bar['y'], $bar['width'],
+ $bar['height'], $item->complete);
+ $bar_parts = '';
+ $b1 = $element;
+ $b1['fill'] = $colour_complete;
+ $b1['clip-path'] = "url(#{$c[0]})";
+ $bar_parts .= $this->element($type, $b1);
+
+ // % remaining
+ $b2 = $element;
+ $b2['fill'] = $colour_incomplete;
+ $b2['clip-path'] = "url(#{$c[1]})";
+ $bar_parts .= $this->element($type, $b2);
+
+ // outline over top
+ $element['fill'] = 'none';
+ $this->setStroke($element, $item, $index, 0);
+ $bar_parts .= $this->element($type, $element);
+
+ return ['element' => 'g', 'content' => $bar_parts];
+ }
+
+ /**
+ * Returns a pair of clip path IDs for clipping bar
+ */
+ protected function getClippers($x, $y, $w, $h, $percent)
+ {
+ $extra = 5;
+ $m1 = $w * $percent / 100;
+ $m2 = $w - $m1;
+
+ $r = [
+ 'x' => $x - $extra,
+ 'y' => $y - $extra,
+ 'width' => $m1 + $extra,
+ 'height' => $h + $extra
+ ];
+ $c = ['id' => $this->newID()];
+ $this->defs->add($this->element('clipPath', $c, null,
+ $this->element('rect', $r)));
+ $clippers = [$c['id']];
+
+ $r['x'] = $x + $m1;
+ $r['width'] = $m2 + $extra;
+ $c = ['id' => $this->newID()];
+ $this->defs->add($this->element('clipPath', $c, null,
+ $this->element('rect', $r)));
+ $clippers[] = $c['id'];
+
+ return $clippers;
+ }
+
+ /**
+ * Returns the colour for the milestone
+ */
+ protected function getMilestoneColour(DataItem $item, $index, $dataset)
+ {
+ $gpat = !($this->getOption('marker_solid', true));
+ $mcolour = $this->getItemOption('gantt_milestone_colour', $dataset, $item, 'colour');
+
+ // don't use per-dataset global colours, only used for complete bars
+ $dataset = 0;
+ if(empty($mcolour))
+ return $this->getColour(null, $index, $dataset, $gpat, $gpat);
+
+ // support fill and fillColour
+ $cg = new ColourGroup($this, $item, $index, $dataset, 'gantt_milestone_colour', null, 'colour');
+ $fill = $cg->stroke();
+
+ // impose marker_solid option
+ if(!$gpat)
+ $fill = new Colour($this, $fill, false, false);
+ return $fill;
+ }
+
+ /**
+ * Returns the attributes of a milestone
+ */
+ protected function getMilestone(DataItem $item, $index, $dataset, $bar)
+ {
+ $fill = $this->getMilestoneColour($item, $index, $dataset);
+ $size = max(2, $this->getItemOption('gantt_milestone_size', $dataset, $item, 'size'));
+ $type = $this->getItemOption('gantt_milestone_type', $dataset, $item, 'type');
+
+ $marker = [
+ 'type' => $type,
+ 'x' => $bar['x'],
+ 'y' => $bar['y'] + $bar['height'] / 2,
+ 'fill' => $fill,
+ 'size' => $size,
+ ];
+ $this->setStroke($marker, $item, $index, $dataset);
+ return $marker;
+ }
+
+ /**
+ * Returns an arrow pointing to where the data is off the display, or null if it is not
+ */
+ protected function getPointer(DataItem $item, $index, $dataset, $bar)
+ {
+ $angle = 0;
+ $pos_start = $this->gridX($item->value);
+ $pos_end = $item->milestone ? $pos_start : $this->gridX($item->end);
+
+ $size = $bar['height'] / 2;
+ $offset = 3;
+ if($pos_start > $this->width - $this->pad_right) {
+ $x = $this->width - $this->pad_right - $size - $offset;
+ $angle = 90;
+ }
+ if($pos_end < $this->pad_left) {
+ $x = $this->pad_left + $size + $offset;
+ $angle = 270;
+ }
+
+ if($angle === 0)
+ return null;
+
+ if($item->milestone) {
+ $fill = $this->getMilestoneColour($item, $index, $dataset);
+ } else {
+ $colours = $this->getBarColours($item, $index, $dataset);
+ $fill = $item->complete >= 100 ? $colours[1] : $colours[0];
+ }
+
+ $marker = [
+ 'type' => 'triangle',
+ 'x' => $x,
+ 'y' => $bar['y'] + $bar['height'] / 2,
+ 'fill' => $fill,
+ 'size' => $size,
+ 'angle' => $angle,
+ ];
+ $this->setStroke($marker, $item, $index, $dataset);
+ return $marker;
+ }
+
+ /**
+ * Draws dependency arrows
+ */
+ protected function drawDependencies(&$item, $index, $dataset, $bar)
+ {
+ // add this bar to the list so others can draw arrows to it
+ if($dataset == 0)
+ $this->bar_list[$item->key] = $bar;
+ $this->bar_list[$item->key . ":" . new Number($dataset)] = $bar;
+ if(!isset($item->depends))
+ return '';
+
+ $arrows = '';
+ $depends = is_array($item->depends) ? $item->depends : [$item->depends];
+ $dtype = is_array($item->depends_type) ? $item->depends_type : [$item->depends_type];
+
+ $head_size = $this->getItemOption('gantt_depends_head_size', $dataset,
+ $item, 'depends_head_size');
+ $stroke_width = min(10, max(0.1,
+ $this->getItemOption('gantt_depends_stroke_width', $dataset, $item, 'depends_stroke_width')));
+ $cg = new ColourGroup($this, $item, $index, $dataset, 'gantt_depends_colour', null, 'depends_colour');
+ $colour = $cg->stroke();
+ $dash = $this->getItemOption('gantt_depends_dash', $dataset, $item, 'depends_dash');
+ $opacity = min(1, max(0,
+ $this->getItemOption('gantt_depends_opacity', $dataset, $item, 'depends_opacity')));
+
+ $group_style = [ 'stroke' => $colour, ];
+ if($stroke_width != 1)
+ $group_style['stroke-width'] = $stroke_width;
+ if(!empty($dash))
+ $group_style['stroke-dasharray'] = $dash;
+ if($opacity < 1)
+ $group_style['opacity'] = $opacity;
+
+ foreach($depends as $k => $d) {
+ if(!isset($this->bar_list[$d]))
+ break;
+
+ $dbar = $this->bar_list[$d];
+ $arrow = new GanttArrow(new Point($dbar['x'], $dbar['y']),
+ new Point($bar['x'], $bar['y']),
+ $dbar['width'], $dbar['height'],
+ $bar['width'], $bar['height'],
+ isset($dtype[$k]) ? $dtype[$k] : 'FS',
+ $this->calculated_bar_space);
+
+ $arrow->setHeadSize($head_size);
+ $arrow->setHeadColour($colour);
+ $arrows .= $arrow->draw($this);
+ }
+ $arrows = $this->element('g', $group_style, null, $arrows);
+ return $arrows;
+ }
+
+ /**
+ * Tooltips are a little more complicated on Gantt chart
+ */
+ protected function formatTooltip(&$item, $dataset, $key, $value)
+ {
+ $axis = $this->x_axes[$this->main_x_axis];
+ $format = $this->getOption('tooltip_datetime_format');
+
+ $dt = new \DateTime('@' . $item->value);
+ $text_start = $axis->format($dt, $format);
+ if($item->milestone) {
+ $ttext = $item->axis_text ? $item->axis_text : $key;
+ $ttext .= "\n" . $text_start;
+ return $ttext;
+ }
+
+ $pluralize = function($n, $units) {
+ $str = new Number($n) . ' ' . $units;
+ if($n != 1)
+ $str .= 's';
+ return $str;
+ };
+
+ $dte = new \DateTime('@' . $item->end);
+ $text_end = $axis->format($dte, $format);
+ $ttext = "{$text_start} - {$text_end}";
+ if($this->getOption('gantt_tooltip_duration')) {
+ $days = ceil(($item->end - $item->value) / 86400);
+ $hours = ceil(($item->end - $item->value) / 3600);
+ $mins = ceil(($item->end - $item->value) / 60);
+ if($days > 364) {
+ $years = $days / 365;
+ $ttext .= "\n" . $pluralize($years, "year");
+ } elseif($days > 20) {
+ $weeks = floor($days / 7);
+ $days = $days % 7;
+ $ttext .= "\n" . $pluralize($weeks, "week");
+ if($days) {
+ $ttext .= ", " . $pluralize($days, "day");
+ }
+ } else {
+ $units = $this->getOption('gantt_units');
+ if($units === 'minute' && $hours <= 24) {
+ $ttext .= "\n";
+ $hours = floor($mins / 60);
+ if($hours > 0) {
+ $ttext .= $pluralize($hours, "hour");
+ $mins = $mins % 60;
+ }
+ if($mins > 0) {
+ if($hours > 0)
+ $ttext .= ', ';
+ $ttext .= $pluralize($mins, "minute");
+ }
+ } elseif($units === 'hour') {
+ $ttext .= "\n";
+ $days = floor($hours / 24);
+ if($days > 0) {
+ $ttext .= $pluralize($days, "day");
+ $hours = $hours % 24;
+ }
+ if($hours > 0) {
+ if($days > 0)
+ $ttext .= ', ';
+ $ttext .= $pluralize($hours, "hour");
+ }
+ } else {
+ $ttext .= "\n" . $pluralize($days, "day");
+ }
+ }
+ }
+
+ if($item->complete && $this->getOption('gantt_tooltip_complete')) {
+ $n = new Number(min(100, $item->complete));
+ $ttext .= "\n[{$n}% complete]";
+ }
+
+ return $ttext;
+ }
+
+ /**
+ * Returns TRUE if the item is visible on the graph
+ */
+ public function isVisible($item, $dataset = 0)
+ {
+ if($item->value === null)
+ return false;
+ if($item->milestone)
+ return true;
+ return ($item->end - $item->value != 0);
+ }
+}
diff --git a/classes/vendor/81x/goat1000/svggraph/GradientList.php b/classes/vendor/81x/goat1000/svggraph/GradientList.php
new file mode 100644
index 0000000..5059efa
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/GradientList.php
@@ -0,0 +1,213 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * Creates and stores gradients
+ */
+class GradientList {
+
+ private $graph;
+ private $gradients = [];
+ private $gradient_map = [];
+
+ public function __construct(&$graph)
+ {
+ $this->graph =& $graph;
+ }
+
+ /**
+ * Adds a gradient to the list, returning the element ID for use in url
+ */
+ public function addGradient($colours, $key = null, $radial = false)
+ {
+ if($key === null || !isset($this->gradients[$key])) {
+
+ if($radial) {
+ // if this is a radial gradient, it must end with 'r'
+ $last = count($colours) - 1;
+ if(strlen($colours[$last]) == 1)
+ $colours[$last] = 'r';
+ else
+ $colours[] = 'r';
+ }
+
+ // find out if this gradient already stored
+ $hash = md5(serialize($colours));
+ if(isset($this->gradient_map[$hash]))
+ return $this->gradient_map[$hash];
+
+ $id = $this->graph->newID();
+ if($key === null)
+ $key = $id;
+ $this->gradients[$key] = ['id' => $id, 'colours' => $colours];
+ $this->gradient_map[$hash] = $id;
+ return $id;
+ }
+ return $this->gradients[$key]['id'];
+ }
+
+ /**
+ * Creates a gradient element
+ */
+ private function makeGradient($key)
+ {
+ $stops = '';
+ $direction = 'v';
+ $type = 'linearGradient';
+ $colours = $this->gradients[$key]['colours'];
+ $id = $this->gradients[$key]['id'];
+
+ if(in_array($colours[count($colours)-1], ['h', 'v', 'r']))
+ $direction = array_pop($colours);
+ if($direction == 'r') {
+ $type = 'radialGradient';
+ $gradient = ['id' => $id];
+ } else {
+ $x2 = $direction == 'v' ? 0 : '100%';
+ $y2 = $direction == 'h' ? 0 : '100%';
+ $gradient = ['id' => $id, 'x1' => 0, 'x2' => $x2, 'y1' => 0, 'y2' => $y2];
+ }
+
+ $segments = $this->decompose($colours);
+ foreach($segments as $segment) {
+ list($offset, $colour, $opacity) = $segment;
+ $stop = ['offset' => $offset . '%', 'stop-color' => $colour];
+ if(is_numeric($opacity))
+ $stop['stop-opacity'] = $opacity;
+ $stops .= $this->graph->element('stop', $stop);
+ }
+
+ return $this->graph->element($type, $gradient, null, $stops);
+ }
+
+ /**
+ * Calculates a colour component from a gradient step
+ */
+ private function calcComponent($c0, $c1, $o0, $o1, $d)
+ {
+ return $c0 + ($c1 - $c0) * ($d - $o0) / ($o1 - $o0);
+ }
+
+ /**
+ * Returns the colour at a position in the gradient
+ */
+ public function getColour($key, $position)
+ {
+ if(!isset($this->gradients[$key]))
+ return '';
+ $colours = $this->gradients[$key]['colours'];
+ if(in_array($colours[count($colours)-1], ['h', 'v', 'r']))
+ $direction = array_pop($colours);
+ $segments = $this->decompose($colours);
+
+ // function to produce colour string
+ $make_colour = function($r, $g, $b, $o) {
+ $str = "rgb({$r},{$g},{$b})";
+ if(!is_numeric($o) || $o >= 1)
+ return $str;
+ if($o <= 0)
+ return 'none';
+ $str .= ':' . $o;
+ return $str;
+ };
+
+ $position = min(100, max(0, $position));
+ $seg0 = $segments[0];
+ $seg1 = null;
+ foreach($segments as $seg) {
+ if(abs($seg[0] - $position) < 0.1) {
+ // stop colour at or close to requested position
+ $c = $seg[1]->rgb();
+ $c_string = $make_colour($c[0], $c[1], $c[2], $seg[2]);
+ $colour = new Colour($this->graph, $c_string);
+ return $colour;
+ }
+
+ if($seg[0] > $position) {
+ $seg1 = $seg;
+ break;
+ }
+ $seg0 = $seg;
+ }
+ if($seg1 === null) {
+ $seg1 = array_pop($segments);
+ $seg1[0] = 100;
+ }
+
+ $c0 = $seg0[1]->rgb();
+ $c1 = $seg1[1]->rgb();
+ $vr = $this->calcComponent($c0[0], $c1[0], $seg0[0], $seg1[0], $position);
+ $vg = $this->calcComponent($c0[1], $c1[1], $seg0[0], $seg1[0], $position);
+ $vb = $this->calcComponent($c0[2], $c1[2], $seg0[0], $seg1[0], $position);
+
+ $o0 = $seg0[2] === null ? 1 : $seg0[2];
+ $o1 = $seg1[2] === null ? 1 : $seg1[2];
+ $vo = $this->calcComponent($o0, $o1, $seg0[0], $seg1[0], $position);
+ $c_string = $make_colour($vr, $vg, $vb, $vo);
+ $colour = new Colour($this->graph, $c_string);
+ return $colour;
+ }
+
+ /**
+ * Breaks gradient array down into components
+ */
+ public function decompose($colours)
+ {
+ $col_mul = 100 / (count($colours) - 1);
+ $offset = 0;
+ $decomposed = [];
+ foreach($colours as $pos => $colour) {
+ $opacity = null;
+ $poffset = $pos * $col_mul;
+ if(strpos($colour, ':') !== false) {
+ // opacity, stop offset or both
+ $parts = explode(':', $colour);
+ if(is_numeric($parts[0]) || count($parts) == 3) {
+ $poffset = array_shift($parts);
+ }
+ // stick the other parts back together and let the Colour class
+ // figure it out
+ $colour = new Colour($this->graph, implode(':', $parts));
+ $opacity = $colour->opacity();
+ if($opacity == 1)
+ $opacity = null;
+ } else {
+ $colour = new Colour($this->graph, $colour);
+ }
+ // set the offset to the most meaningful number
+ $offset = min(100, max(0, $offset, $poffset));
+ $decomposed[] = [$offset, $colour, $opacity];
+ }
+ return $decomposed;
+ }
+
+ /**
+ * Defines the list of gradients
+ */
+ public function makeGradients(&$defs)
+ {
+ foreach($this->gradients as $key => $gradient)
+ $defs->add($this->makeGradient($key));
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/Graph.php b/classes/vendor/81x/goat1000/svggraph/Graph.php
new file mode 100644
index 0000000..fb9b530
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/Graph.php
@@ -0,0 +1,1605 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * Base class for all graph types
+ */
+abstract class Graph {
+
+ public $subgraph = false;
+ protected $width = 0;
+ protected $height = 0;
+ protected $pad_left = 0;
+ protected $pad_right = 0;
+ protected $pad_top = 0;
+ protected $pad_bottom = 0;
+ protected $settings = [];
+ protected $values = [];
+ protected $namespace = false;
+ protected $link_base = '';
+ protected $link_target = '_blank';
+ protected $links = [];
+ public $encoding = 'UTF-8';
+
+ protected $colours = null;
+ public $defs = null;
+ public $figures = null;
+ protected $subgraphs = [];
+ protected $back_matter = '';
+
+ protected $namespaces = [];
+ private static $last_id = 0;
+ public static $key_format = null;
+ protected $legend = null;
+ protected $data_label_style_cache = [];
+ protected $multi_graph;
+
+ private static $javascript = null;
+ private $data_labels = null;
+ private $context_menu = null;
+ private $shapes = null;
+
+ /**
+ * @arg $w = width
+ * @arg $h = height
+ * @arg $settings = user options
+ * @arg $fixed_settings = class options overriding user options
+ */
+ public function __construct($w, $h, array $settings, array $fixed_settings = [])
+ {
+ $this->width = $w;
+ $this->height = $h;
+ $this->defs = new Defs($this);
+
+ // get settings from ini file that are relevant to this class
+ $class = get_class($this);
+ $ini_settings = $this->ini_settings($class);
+
+ // default option overrides - subclasses can override these
+ $fixed_setting_defaults = [
+ 'repeated_keys' => 'error',
+ 'sort_keys' => true,
+ 'require_structured' => false,
+ 'require_integer_keys' => true,
+ ];
+ $this->settings = array_merge($this->settings, $ini_settings, $settings,
+ $fixed_setting_defaults, $fixed_settings);
+
+ // set up figures - it can't be dynamic because figures can refer
+ // to other figures via Markers
+ $this->figures = new Figures($this, $this->settings);
+
+ // copy some settings to member variables
+ $opts = ['namespace', 'link_base', 'link_target', 'encoding',
+ 'pad_top', 'pad_bottom', 'pad_left', 'pad_right'];
+ foreach($opts as $opt)
+ $this->{$opt} = $this->getOption($opt);
+ }
+
+ /**
+ * Deprecated option/member access - display error and fail
+ */
+ public function __get($name)
+ {
+ trigger_error('Attempt to get $this->' . $name, E_USER_WARNING);
+ debug_print_backtrace(0, 1);
+ exit;
+ }
+ public function __isset($name)
+ {
+ trigger_error('Isset attempt on option $this->' . $name, E_USER_WARNING);
+ debug_print_backtrace(0, 1);
+ exit;
+ }
+ public function __set($name, $value)
+ {
+ trigger_error('Attempt to set $this->' . $name, E_USER_WARNING);
+ debug_print_backtrace(0, 1);
+ exit;
+ }
+
+ /**
+ * Returns the settings from the ini file for a class
+ */
+ protected function ini_settings($class)
+ {
+ $ini_file = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'svggraph.ini';
+ $ini_settings = false;
+ if(file_exists($ini_file))
+ $ini_settings = parse_ini_file($ini_file, true);
+ if($ini_settings === false)
+ throw new \Exception('INI file [' . $ini_file . '] could not be loaded');
+
+ $hierarchy = [$class];
+ while($class = get_parent_class($class))
+ array_unshift($hierarchy, $class);
+
+ $settings = [];
+ while(count($hierarchy)) {
+ $class = array_shift($hierarchy);
+ $ns = strrpos($class, '\\');
+ $class = substr($class, $ns + 1);
+ if(array_key_exists($class, $ini_settings))
+ $settings = array_merge($settings, $ini_settings[$class]);
+ }
+
+ return $settings;
+ }
+
+ /**
+ * Sets the graph values
+ */
+ public function values($values)
+ {
+ $new_values = [];
+ $v = func_get_args();
+ if(count($v) == 1)
+ $v = array_shift($v);
+
+ $set_values = true;
+ if(is_array($v)) {
+ reset($v);
+ $first_key = key($v);
+ if($first_key !== null && is_array($v[$first_key])) {
+ foreach($v as $data_set)
+ $new_values[] = $data_set;
+ $set_values = false;
+ }
+ }
+
+ if($set_values)
+ $new_values[] = $v;
+
+ $require_structured = $this->getOption('require_structured');
+ $structured_data = $this->getOption('structured_data');
+ $structure = $this->getOption('structure');
+ $datetime_keys = $this->getOption('datetime_keys');
+ $datetime_key_format = $this->getOption('datetime_key_format');
+ $force_assoc = $this->getOption('force_assoc');
+
+ if($this->getOption('scatter_2d')) {
+ $this->setOption('scatter_2d', false);
+ if(empty($structure)) {
+ $structure = ['key' => 0, 'value' => 1, 'datasets' => true];
+ $this->setOption('structure', $structure);
+ }
+ }
+
+ if($datetime_keys && $datetime_key_format)
+ Graph::$key_format = $datetime_key_format;
+
+ if($structured_data || is_array($structure)) {
+ $this->setOption('structured_data', true);
+ $this->values = new StructuredData($new_values, $force_assoc,
+ $datetime_keys, $structure,
+ $this->getOption('repeated_keys'), $this->getOption('sort_keys'),
+ $this->getOption('require_integer_keys'), $require_structured);
+ } else {
+ $this->values = new Data($new_values, $force_assoc, $datetime_keys);
+ if(!$this->values->error && !empty($require_structured))
+ $this->values->error = get_class($this) . ' requires structured data';
+ }
+
+ if($this->values->error)
+ return;
+
+ $dataset = $this->getOption('dataset', 0);
+ if($dataset === 0)
+ return;
+
+ $dcount = count($this->values);
+ $dataset = $this->getOption(['dataset', 0], 0);
+ if($dcount <= $dataset) {
+ $this->values->error = 'No valid datasets selected';
+ return;
+ }
+
+ // dataset option doesn't work well without structured data
+ $this->values = StructuredData::convertFrom($this->values, $force_assoc,
+ $datetime_keys, $this->getOption('require_integer_keys'));
+ }
+
+ /**
+ * Sets the links from each item
+ */
+ public function links()
+ {
+ $this->links = func_get_args();
+ }
+
+ /**
+ * Assigns the list of subgraphs
+ */
+ public function subgraphs($subgraphs)
+ {
+ $this->subgraphs = $subgraphs;
+ }
+
+ /**
+ * Set up the colours
+ */
+ public function colours(Colours $colours)
+ {
+ $this->colours = $colours;
+ }
+
+ public function getMinValue()
+ {
+ $d = $this->getOption(['dataset', 0], 0);
+ return $this->values->getMinValue($d);
+ }
+ public function getMaxValue()
+ {
+ $d = $this->getOption(['dataset', 0], 0);
+ return $this->values->getMaxValue($d);
+ }
+ public function getMinKey()
+ {
+ $d = $this->getOption(['dataset', 0], 0);
+ return $this->values->getMinKey($d);
+ }
+ public function getMaxKey()
+ {
+ $d = $this->getOption(['dataset', 0], 0);
+ return $this->values->getMaxKey($d);
+ }
+ public function getKey($i)
+ {
+ return $this->values->getKey($i);
+ }
+
+ /**
+ * Sets up the colours used for the graph
+ */
+ protected function setup()
+ {
+ $dataset = $this->getOption(['dataset', 0], 0);
+ $this->colourSetup($this->values->itemsCount($dataset));
+ }
+
+ /**
+ * Returns the Javascript instance
+ */
+ public function getJavascript()
+ {
+ if(!isset(Graph::$javascript))
+ Graph::$javascript = new Javascript($this);
+ return Graph::$javascript;
+ }
+
+ /**
+ * Returns the DataLabels instance
+ */
+ public function getDataLabels()
+ {
+ if($this->data_labels === null)
+ $this->data_labels = new DataLabels($this);
+ return $this->data_labels;
+ }
+
+ /**
+ * Draws the selected graph
+ */
+ public function drawGraph()
+ {
+ $canvas_id = $this->newID();
+ $this->initLegend();
+ $this->setup();
+ if(!is_numeric($this->width))
+ $this->width = 640;
+ if(!is_numeric($this->height))
+ $this->height = 480;
+
+ $contents = $this->canvas($canvas_id);
+ $contents .= $this->drawTitle();
+ $contents .= $this->draw();
+ $contents .= $this->drawDataLabels();
+ $contents .= $this->drawBackMatter();
+ $contents .= $this->drawLegend();
+
+ foreach($this->subgraphs as $subgraph)
+ $contents .= $subgraph->fetch($this);
+
+ // magnifying means everything must be in a group for transformation
+ if(!$this->subgraph && $this->getOption('magnify')) {
+ $this->getJavascript()->magnifier();
+ $group = ['class' => 'svggraph-magnifier'];
+ $contents = $this->element('g', $group, null, $contents);
+ }
+
+ // rounded rects might need a clip path
+ if($this->getOption('back_round') && $this->getOption('back_round_clip')) {
+ $group = ['clip-path' => 'url(#' . $canvas_id . ')'];
+ return $this->element('g', $group, null, $contents);
+ }
+ return $contents;
+ }
+
+ /**
+ * Adds any markup that goes after the graph
+ */
+ protected function drawBackMatter()
+ {
+ return $this->back_matter;
+ }
+
+ /**
+ * Sets up the legend class
+ */
+ protected function initLegend()
+ {
+ $this->legend = null;
+
+ // see if the legend is needed
+ if(!$this->getOption('show_legend'))
+ return;
+
+ $entries = $this->getOption('legend_entries');
+ $structure = $this->getOption('structure');
+ if(empty($entries) && !isset($structure['legend_text']))
+ return;
+
+ $this->legend = new Legend($this);
+ }
+
+ /**
+ * Returns the ordering for legend entries
+ */
+ public function getLegendOrder()
+ {
+ // null for no special order
+ return null;
+ }
+
+ /**
+ * Draws the legend
+ */
+ protected function drawLegend()
+ {
+ if($this->legend === null)
+ return '';
+ return $this->legend->draw();
+ }
+
+ /**
+ * Parses a position string, returning x and y coordinates
+ */
+ public function parsePosition($pos, $w = 0, $h = 0, $pad = 0)
+ {
+ $inner = true;
+ $parts = preg_split('/\s+/', $pos);
+ if(count($parts)) {
+ // if 'outer' is found after 'inner', it takes precedence
+ $parts = array_reverse($parts);
+ $inner_at = array_search('inner', $parts);
+ $outer_at = array_search('outer', $parts);
+
+ if($outer_at !== false && ($inner_at === false || $inner_at < $outer_at))
+ $inner = false;
+ }
+
+ if($inner) {
+ $t = $this->pad_top;
+ $l = $this->pad_left;
+ $b = $this->height - $this->pad_bottom;
+ $r = $this->width - $this->pad_right;
+ // make sure it fits to keep RelativePosition happy
+ if($w > $r - $l) $w = $r - $l;
+ if($h > $b - $t) $h = $b - $t;
+ } else {
+ $t = $l = 0;
+ $b = $this->height;
+ $r = $this->width;
+ }
+
+ // ParsePosition is always inside canvas or graph, defaulted to top left
+ $pos = 'top left ' . str_replace('outer', 'inner', $pos);
+ return Graph::relativePosition($pos, $t, $l, $b, $r, $w, $h, $pad);
+ }
+
+ /**
+ * Returns [hpos,vpos,offset_x,offset_y] positions derived from full
+ * position string
+ */
+ public static function translatePosition($pos)
+ {
+ $parts = preg_split('/\s+/', strtolower($pos));
+ $offset_x = $offset_y = 0;
+ $inside = true;
+ $vpos = 'm';
+ $hpos = 'c';
+
+ // translated positions:
+ // ot, t, m, b, ob = outside top, top, middle, bottom, outside bottom
+ // ol, l, c, r, or = outside left, left, centre, right, outside right
+ while(count($parts)) {
+ $part = array_shift($parts);
+ switch($part) {
+ case 'outer' :
+ case 'outside' : $inside = false;
+ break;
+ case 'inner' :
+ case 'inside' : $inside = true;
+ break;
+ case 'top' : $vpos = $inside ? 't' : 'ot';
+ break;
+ case 'bottom' : $vpos = $inside ? 'b' : 'ob';
+ break;
+ case 'left' : $hpos = $inside ? 'l' : 'ol';
+ break;
+ case 'right' : $hpos = $inside ? 'r' : 'or';
+ break;
+ case 'above' : $inside = false; $vpos = 'ot';
+ break;
+ case 'below' : $inside = false; $vpos = 'ob';
+ break;
+ default:
+ if(is_numeric($part)) {
+ $offset_x = $part;
+ if(count($parts) && is_numeric($parts[0]))
+ $offset_y = array_shift($parts);
+ }
+ }
+ }
+ return [$hpos, $vpos, $offset_x, $offset_y];
+ }
+
+ /**
+ * Returns [x,y,text-anchor,hpos,vpos] position that is $pos relative to the
+ * top, left, bottom and right.
+ * When $text is true, x and y are adjusted for text-anchor position
+ */
+ public static function relativePosition($pos, $top, $left,
+ $bottom, $right, $width, $height, $pad, $text = false)
+ {
+ list($hpos, $vpos, $offset_x, $offset_y) = Graph::translatePosition($pos);
+
+ // if the containers have no thickness, position outside
+ $translate = ['l' => 'ol', 'r' => 'or', 't' => 'ot', 'b' => 'ob'];
+ if($top == $bottom && isset($translate[$vpos]))
+ $vpos = $translate[$vpos];
+ if($left == $right && isset($translate[$hpos]))
+ $hpos = $translate[$hpos];
+
+ switch($vpos) {
+ case 'ot' : $y = $top - $height - $pad; break;
+ case 't' : $y = $top + $pad; break;
+ case 'b' : $y = $bottom - $height - $pad; break;
+ case 'ob' : $y = $bottom + $pad; break;
+ case 'm' :
+ default :
+ $y = $top + ($bottom - $top - $height) / 2; break;
+ }
+
+ if(($hpos == 'r' || $hpos == 'l') && $right - $left - $pad - $width < 0)
+ $hpos = 'c';
+ switch($hpos) {
+ case 'ol' : $x = $left - $width - $pad; break;
+ case 'l' : $x = $left + $pad; break;
+ case 'r' : $x = $right - $width - $pad; break;
+ case 'or' : $x = $right + $pad; break;
+ case 'c' :
+ default :
+ $x = $left + ($right - $left - $width) / 2; break;
+ }
+
+ $y += $offset_y;
+ $x += $offset_x;
+
+ // third return value is text alignment
+ $align_map = [
+ 'ol' => 'end', 'l' => 'start', 'c' => 'middle',
+ 'r' => 'end', 'or' => 'start'
+ ];
+ $text_align = $align_map[$hpos];
+
+ // in text mode, adjust X for text alignment
+ if($text && $hpos != 'l' && $hpos != 'or') {
+ if($hpos == 'c')
+ $x += $width / 2;
+ else
+ $x += $width;
+ }
+ return [$x, $y, $text_align, $hpos, $vpos];
+ }
+
+ /**
+ * Sets the style info for the legend
+ */
+ protected function setLegendEntry($dataset, $index, $item, $style_info)
+ {
+ if($this->legend === null)
+ return;
+ $this->legend->setEntry($dataset, $index, $item, $style_info);
+ }
+
+ /**
+ * Subclasses must draw the entry, if they can
+ */
+ protected function drawLegendEntry($x, $y, $w, $h, $entry)
+ {
+ return '';
+ }
+
+ /**
+ * Returns details of title and subtitle
+ */
+ protected function getTitle()
+ {
+ $info = [
+ 'title' => $this->getOption('graph_title'),
+ 'font' => $this->getOption('graph_title_font'),
+ 'font_size' => 0, // 0 font size = no title
+ 'sfont_size' => 0, // 0 = no subtitle
+ ];
+
+ $svg_text = new Text($this, $info['font']);
+ if($svg_text->strlen($info['title']) <= 0)
+ return $info;
+
+ $info['font_size'] = Number::units($this->getOption('graph_title_font_size'));
+ $info['weight'] = $this->getOption('graph_title_font_weight');
+ $info['colour'] = $this->getOption('graph_title_colour');
+ $info['pos'] = $this->getOption('graph_title_position');
+ $info['space'] = $this->getOption('graph_title_space');
+ $info['line_spacing'] = Number::units($this->getOption('graph_title_line_spacing'));
+ if($info['line_spacing'] === null || $info['line_spacing'] < 1)
+ $info['line_spacing'] = $info['font_size'];
+
+ if($info['pos'] != 'bottom' && $info['pos'] != 'left' && $info['pos'] != 'right')
+ $info['pos'] = 'top';
+ list($width, $height) = $svg_text->measure($info['title'], $info['font_size'],
+ 0, $info['line_spacing']);
+ $info['width'] = $width;
+ $info['height'] = $height;
+
+ // now deal with sub-title
+ $info['stitle'] = $this->getOption('graph_subtitle');
+ $info['sfont'] = $this->getOption('graph_subtitle_font', 'graph_title_font');
+ $svg_subtext = new Text($this, $info['sfont']);
+
+ if($svg_subtext->strlen($info['stitle']) > 0) {
+ $info['sfont_size'] = Number::units($this->getOption('graph_subtitle_font_size',
+ 'graph_title_font_size'));
+ $info['sweight'] = $this->getOption('graph_subtitle_font_weight',
+ 'graph_title_font_weight');
+ $info['scolour'] = $this->getOption('graph_subtitle_colour',
+ 'graph_title_colour');
+ $info['sspace'] = $this->getOption('graph_subtitle_space',
+ 'graph_title_space');
+ $info['sline_spacing'] = Number::units($this->getOption('graph_subtitle_line_spacing'));
+ if($info['sline_spacing'] === null || $info['sline_spacing'] < 1)
+ $info['sline_spacing'] = $info['sfont_size'];
+
+ list($swidth, $sheight) = $svg_subtext->measure($info['stitle'],
+ $info['sfont_size'], 0, $info['sline_spacing']);
+ $info['swidth'] = $swidth;
+ $info['sheight'] = $sheight;
+ }
+
+ return $info;
+ }
+
+ /**
+ * Draws the graph title, if there is one
+ */
+ protected function drawTitle()
+ {
+ $info = $this->getTitle();
+ if($info['font_size'] == 0)
+ return '';
+
+ $svg_text = new Text($this, $info['font']);
+ $baseline = $svg_text->baseline($info['font_size']);
+ $text = [
+ 'font-size' => $info['font_size'],
+ 'font-family' => $info['font'],
+ 'font-weight' => $info['weight'],
+ 'text-anchor' => 'middle',
+ 'fill' => new Colour($this, $info['colour']),
+ ];
+
+ // ensure outside padding is at least the title space
+ $pad_side = 'pad_' . $info['pos'];
+ if($this->{$pad_side} < $info['space'])
+ $this->{$pad_side} = $info['space'];
+
+ $xform = new Transform;
+ switch($info['pos']) {
+ case 'left':
+ $text['x'] = $this->pad_left + $baseline;
+ $text['y'] = $this->height / 2;
+ $xform->rotate(270, $text['x'], $text['y']);
+ $text['transform'] = $xform;
+ break;
+ case 'right':
+ $text['x'] = $this->width - $this->pad_right - $baseline;
+ $text['y'] = $this->height / 2;
+ $xform->rotate(90, $text['x'], $text['y']);
+ $text['transform'] = $xform;
+ break;
+ case 'bottom':
+ $text['x'] = $this->width / 2;
+ $text['y'] = $this->height - $this->pad_bottom - $info['height'] + $baseline;
+ break;
+ default:
+ $text['x'] = $this->width / 2;
+ $text['y'] = $this->pad_top + $baseline;
+ }
+ // increase padding by size of text
+ $this->{$pad_side} += $info['height'] + $info['space'];
+
+ // now deal with sub-title
+ $subtitle_text = '';
+ if($info['sfont_size'] != 0) {
+
+ $svg_subtext = new Text($this, $info['sfont']);
+ $sbaseline = $svg_subtext->baseline($info['sfont_size']);
+ $stext = [
+ 'font-size' => $info['sfont_size'],
+ 'font-family' => $info['sfont'],
+ 'font-weight' => $info['sweight'],
+ 'text-anchor' => 'middle',
+ 'fill' => new Colour($this, $info['scolour']),
+ ];
+
+ $sxform = new Transform;
+ $sub_offset = $sbaseline + $info['sspace'] - $info['space'];
+ switch($info['pos']) {
+ case 'left':
+ $stext['x'] = $this->pad_left + $sub_offset;
+ $stext['y'] = $text['y'];
+ $sxform->rotate(270, $stext['x'], $stext['y']);
+ $stext['transform'] = $sxform;
+ break;
+ case 'right':
+ $stext['x'] = $this->width - $this->pad_right - $sub_offset;
+ $stext['y'] = $text['y'];
+ $sxform->rotate(90, $stext['x'], $stext['y']);
+ $stext['transform'] = $sxform;
+ break;
+ case 'bottom':
+ // complicates things - need to shift title up
+ $stext['x'] = $text['x'];
+ $stext['y'] = $text['y'] - $baseline + $info['height'] + $sbaseline - $info['sheight'];
+ $text['y'] -= $info['sheight'] + $info['sspace'];
+ break;
+ default:
+ $stext['x'] = $text['x'];
+ $stext['y'] = $this->pad_top + $sub_offset;
+ }
+
+ $this->{$pad_side} += $info['sheight'] + $info['sspace'];
+ $subtitle_text = $svg_subtext->text($info['stitle'], $info['sline_spacing'], $stext);
+ }
+
+ // the Text function will break it into lines
+ $title_text = $svg_text->text($info['title'], $info['line_spacing'], $text);
+
+ return $title_text . $subtitle_text;
+ }
+
+ /**
+ * This should be overridden by subclass!
+ */
+ abstract protected function draw();
+
+ /**
+ * Displays the background image
+ */
+ protected function backgroundImage($shadow, $clip_path)
+ {
+ $image_src = $this->getOption('back_image');
+ if(!$image_src)
+ return '';
+
+ $width = $this->getOption('back_image_width');
+ $height = $this->getOption('back_image_height');
+ $left = $this->getOption('back_image_left');
+ $top = $this->getOption('back_image_top');
+ $mode = $this->getOption('back_image_mode');
+
+ if($shadow) {
+ if($width === '100%')
+ $width = new Number($this->width - $shadow);
+ if($height === '100%')
+ $height = new Number($this->height - $shadow);
+ }
+
+ $image = [
+ 'width' => $width, 'height' => $height,
+ 'x' => $left, 'y' => $top,
+ 'xlink:href' => $image_src,
+ 'preserveAspectRatio' =>
+ ($mode == 'stretch' ? 'none' : 'xMinYMin')
+ ];
+
+ if($clip_path !== null)
+ $image['clip-path'] = 'url(#' . $clip_path . ')';
+
+ $style = [];
+ if($this->getOption('back_image_opacity'))
+ $style['opacity'] = $this->getOption('back_image_opacity');
+
+ $contents = '';
+ if($mode == 'tile') {
+ $image['x'] = 0; $image['y'] = 0;
+ $im = $this->element('image', $image, $style);
+ $pattern = [
+ 'id' => $this->newID(),
+ 'width' => $width, 'height' => $height,
+ 'x' => $left, 'y' => $top,
+ 'patternUnits' => 'userSpaceOnUse'
+ ];
+ // tiled image becomes a pattern to replace background colour
+ $this->defs->add($this->element('pattern', $pattern, null, $im));
+ $this->setOption('back_colour', 'url(#' . $pattern['id'] . ')');
+ } else {
+ $im = $this->element('image', $image, $style);
+ $contents .= $im;
+ }
+ return $contents;
+ }
+
+ /**
+ * Displays the background
+ */
+ protected function canvas($id)
+ {
+ $round = $this->getOption('back_round');
+ $stroke_width = $this->getOption('back_stroke_width');
+ $stroke_colour = new Colour($this, $this->getOption('back_stroke_colour'));
+ $shadow_opacity = $this->getOption('back_shadow_opacity');
+ $shadow_blur = $this->getOption('back_shadow_blur');
+ $shadow = $this->getOption('back_shadow');
+ if($shadow === true)
+ $shadow = 2;
+
+ // background image can replace the back_colour option
+ $bg = $this->backgroundImage($shadow, $round ? $id : null);
+ $colour = new Colour($this, $this->getOption('back_colour'));
+
+ $canvas = [
+ 'width' => '100%', 'height' => '100%',
+ 'fill' => $colour,
+ 'stroke-width' => 0,
+ ];
+ $c_el = '';
+
+ if($shadow) {
+ // shadow means canvas cannot be 100% of document
+ $canvas['x'] = $canvas['y'] = new Number($stroke_width / 2);
+
+ // blurring means using a filter and a larger offset
+ if($shadow_blur) {
+ $filter_opts = [
+ 'offset_x' => $shadow,
+ 'offset_y' => $shadow,
+ 'blur' => $shadow_blur,
+ 'opacity' => $shadow_opacity,
+ 'shadow_only' => true,
+ ];
+
+ $shadow += $shadow_blur;
+ $filter = $this->defs->addFilter('shadow', $filter_opts);
+ }
+ $canvas['width'] = new Number($this->width - $shadow - $stroke_width);
+ $canvas['height'] = new Number($this->height - $shadow - $stroke_width);
+
+ $filled = [
+ //'x' => $shadow, 'y' => $shadow,
+ 'width' => new Number($this->width - $shadow),
+ 'height' => new Number($this->height - $shadow),
+ 'fill' => '#000',
+ 'stroke-width' => 0,
+ //'opacity' => $shadow_opacity,
+ ];
+
+ if($shadow_blur) {
+ $filled['filter'] = 'url(#' . $filter . ')';
+ } else {
+ $filled['x'] = $shadow;
+ $filled['y'] = $shadow;
+ $filled['opacity'] = $shadow_opacity;
+ }
+
+ if($round)
+ $filled['rx'] = $filled['ry'] = new Number($round);
+
+ $c_el .= $this->element('rect', $filled);
+
+ // increase padding to clear shadow
+ $this->pad_right += $shadow;
+ $this->pad_bottom += $shadow;
+ }
+
+ if($colour->opacity() < 1)
+ $canvas['opacity'] = $colour->opacity(true);
+
+ if($round)
+ $canvas['rx'] = $canvas['ry'] = new Number($round);
+ if($bg == '' && $stroke_width) {
+ $canvas['stroke-width'] = $stroke_width;
+ $canvas['stroke'] = $stroke_colour;
+ }
+ $c_el .= $this->element('rect', $canvas);
+
+ // create a clip path for rounded rectangle
+ if($round) {
+ $this->defs->add($this->element('clipPath', ['id' => $id], null,
+ $this->element('rect', $canvas)));
+ }
+
+ // if the background image is an element, insert it between the background
+ // colour and border rect
+ if($bg != '') {
+ $c_el .= $bg;
+ if($stroke_width) {
+ $canvas['stroke-width'] = $stroke_width;
+ $canvas['stroke'] = $stroke_colour;
+ $canvas['fill'] = 'none';
+ $c_el .= $this->element('rect', $canvas);
+ }
+ }
+
+ return $c_el;
+ }
+
+ /**
+ * Displays readable (hopefully) error message
+ */
+ protected function errorText($error)
+ {
+ if(!is_numeric($this->height))
+ $this->height = 100;
+ $text = ['x' => 3, 'y' => $this->height - 3];
+ $style = [
+ 'font-family' => 'Courier New',
+ 'font-size' => '11px',
+ 'font-weight' => 'bold',
+ ];
+
+ $e = $this->contrastText($text['x'], $text['y'], $error, 'blue',
+ 'white', $style);
+ return $e;
+ }
+
+ /**
+ * Displays high-contrast text
+ */
+ protected function contrastText($x, $y, $text, $fcolour = 'black',
+ $bcolour = 'white', $properties = null, $styles = null)
+ {
+ $xform = new Transform;
+ $xform->translate($x, $y);
+ $props = ['transform' => $xform, 'fill' => $fcolour];
+ if(is_array($properties))
+ $props = array_merge($properties, $props);
+
+ $bg = $this->element('text', ['stroke-width' => '2px', 'stroke' => $bcolour],
+ null, $text);
+ $fg = $this->element('text', null, null, $text);
+ return $this->element('g', $props, $styles, $bg . $fg);
+ }
+
+ /**
+ * Builds an element
+ */
+ public function element($name, $attribs = null, $styles = null,
+ $content = null, $no_whitespace = false)
+ {
+ if($this->namespace && strpos($name, ':') === false)
+ $name = 'svg:' . $name;
+ $element = '<' . $name;
+ if(is_array($attribs)) {
+ foreach($attribs as $attr => $val) {
+ $value = new Attribute($attr, $val, $this->encoding);
+ $element .= ' ' . $attr . '="' . $value . '"';
+ }
+ }
+
+ if(is_array($styles)) {
+ $element .= ' style="';
+ foreach($styles as $attr => $val) {
+ $value = new Attribute($attr, $val, $this->encoding);
+ $element .= $attr . ':' . $value . ';';
+ }
+ $element .= '"';
+ }
+
+ if($content === null)
+ $element .= "/>";
+ else
+ $element .= '>' . $content . '' . $name . ">";
+ if(!$no_whitespace)
+ $element .= "\n";
+
+ return $element;
+ }
+
+ /**
+ * Returns a link URL or NULL if none
+ */
+ public function getLinkURL($item, $key, $row = 0)
+ {
+ $link = ($item === null ? null : $item->link);
+ if(is_numeric($key))
+ $key = (int)round($key);
+ if($link === null && is_array($this->links[$row]) &&
+ isset($this->links[$row][$key])) {
+ $link = $this->links[$row][$key];
+ }
+
+ // check for absolute links
+ if($link !== null && strpos($link,'//') === false)
+ $link = $this->link_base . $link;
+
+ return $link;
+ }
+
+ /**
+ * Retrieves a link
+ */
+ public function getLink($item, $key, $content, $row = 0)
+ {
+ $link = $this->getLinkURL($item, $key, $row);
+ if($link === null)
+ return $content;
+
+ $link_attr = ['xlink:href' => $link];
+ if(!empty($this->link_target))
+ $link_attr['target'] = $this->link_target;
+ return $this->element('a', $link_attr, null, $content);
+ }
+
+ /**
+ * Returns TRUE if the item is visible on the graph
+ */
+ public function isVisible($item, $dataset = 0)
+ {
+ // default implementation is for all non-zero values to be visible
+ return ($item->value != 0);
+ }
+
+ /**
+ * Sets up the colour class
+ */
+ protected function colourSetup($count, $datasets = null, $reverse = false)
+ {
+ $this->colours->setup($count, $datasets, $reverse);
+ }
+
+ /**
+ * Returns a Colour
+ */
+ public function getColour($item, $key, $dataset, $allow_gradient = true,
+ $allow_pattern = true)
+ {
+ if($item !== null && $item->colour !== null)
+ return new Colour($this, $item->colour, $allow_gradient, $allow_pattern);
+
+ $c = $this->colours->getColour($key, $dataset);
+ if($c === null)
+ return new Colour($this, null);
+
+ $colour = new Colour($this, $c, $allow_gradient, $allow_pattern);
+
+ // make key reflect dataset as well (for gradients)
+ if($dataset !== null)
+ $key = $dataset . ':' . $key;
+ if($key !== null)
+ $colour->setGradientKey($key);
+
+ return $colour;
+ }
+
+ /**
+ * Returns the first non-empty option in named argument list.
+ * Arguments must be "opt_name" or array("opt_name", $index), optionally
+ * ending with default value (non-string or array('@', $value))
+ */
+ public function getOption($opt, $opt2 = null)
+ {
+ // single option - checking for null second option is faster
+ // than using func_num_args()
+ if($opt2 === null && is_string($opt)) {
+ if(isset($this->settings[$opt]) && $this->settings[$opt] !== '')
+ return $this->settings[$opt];
+ return null;
+ }
+
+ $opts = func_get_args();
+ foreach($opts as $opt) {
+ // not string or array, default value
+ if(!is_array($opt) && !is_string($opt))
+ return $opt;
+
+ if(is_array($opt)) {
+ // validate
+ if(!isset($opt[0]) || !isset($opt[1]) || !is_string($opt[0]))
+ throw new \InvalidArgumentException('Malformed option array');
+
+ // default value
+ if($opt[0] === '@')
+ return $opt[1];
+
+ list($name, $index) = $opt;
+ if(isset($this->settings[$name])) {
+ $val = $this->settings[$name];
+ if(is_array($val)) {
+
+ if(isset($val[$index])) {
+ $val = $val[$index];
+ } else {
+ if(!is_numeric($index))
+ $index = 0;
+ $val = $val[$index % count($val)];
+ }
+ }
+
+ if($val !== null && $val !== '')
+ return $val;
+ }
+ continue;
+ }
+
+ // not an array
+ if(isset($this->settings[$opt])) {
+ $val = $this->settings[$opt];
+ if($val !== null && $val !== '')
+ return $val;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns option from data item if present, or from settings if not
+ */
+ public function getItemOption($option, $dataset, &$item, $item_option = null)
+ {
+ if($item_option === null)
+ $item_option = $option;
+ $value = null;
+ if($item !== null)
+ $value = $item->data($item_option);
+ if($value === null)
+ $value = $this->getOption([$option, $dataset]);
+ return $value;
+ }
+
+ /**
+ * Option setter
+ */
+ public function setOption($name, $value, $index = null)
+ {
+ // very simple for now, might have to revisit it later
+ if($index === null) {
+ $this->settings[$name] = $value;
+ return;
+ }
+ $this->settings[$name][$index] = $value;
+ }
+
+ /**
+ * Returns the graph size and padding
+ */
+ public function getDimensions()
+ {
+ $dimensions = [
+ 'width' => $this->width,
+ 'height' => $this->height,
+ 'pad_left' => $this->pad_left,
+ 'pad_top' => $this->pad_top,
+ 'pad_right' => $this->pad_right,
+ 'pad_bottom' => $this->pad_bottom,
+ ];
+ return $dimensions;
+ }
+
+ /**
+ * Checks that the data are valid
+ */
+ protected function checkValues()
+ {
+ if($this->values->error)
+ throw new \Exception($this->values->error);
+ }
+
+ /**
+ * Sets the stroke options for an element
+ */
+ protected function setStroke(&$attr, &$item, $key, $dataset, $line_join = null)
+ {
+ unset($attr['stroke'], $attr['stroke-width'], $attr['stroke-linejoin'],
+ $attr['stroke-dasharray']);
+
+ $stroke_width = $this->getItemOption('stroke_width', $dataset, $item);
+ if($stroke_width > 0) {
+ $cg = new ColourGroup($this, $item, $key, $dataset);
+ $attr['stroke'] = $cg->stroke();
+
+ if($attr['stroke']->opacity() < 1)
+ $attr['stroke-opacity'] = $attr['stroke']->opacity(true);
+ $attr['stroke-width'] = $stroke_width;
+ if($line_join !== null)
+ $attr['stroke-linejoin'] = $line_join;
+
+ $dash = $this->getItemOption('stroke_dash', $dataset, $item);
+ if(!empty($dash))
+ $attr['stroke-dasharray'] = $dash;
+ }
+ }
+
+ /**
+ * Creates a new ID for an element
+ */
+ public function newID()
+ {
+ $prefix = (string)$this->getOption('id_prefix');
+ $i = ++Graph::$last_id;
+
+ // id is case sensitive, so use lower and upper case
+ $chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
+ $iid = [];
+ while($i > 0) {
+ $c = $i % 62;
+ $i = floor($i / 62);
+ $iid[] = $chars[$c];
+ }
+ return $prefix . 'e' . implode(array_reverse($iid));
+ }
+
+ /**
+ * Adds markup to be inserted between graph and legend
+ */
+ public function addBackMatter($fragment)
+ {
+ $this->back_matter .= $fragment;
+ }
+
+ /**
+ * Adds context menu for item
+ */
+ protected function setContextMenu(&$element, $dataset, &$item,
+ $duplicate = false)
+ {
+ if($this->context_menu === null) {
+ $js = $this->getJavascript();
+ $this->context_menu = new ContextMenu($this, $js);
+ }
+ $this->context_menu->setMenu($element, $dataset, $item, $duplicate);
+ }
+
+ /**
+ * Default tooltip contents are key and value, or whatever
+ * $key is if $value is not set
+ */
+ protected function setTooltip(&$element, &$item, $dataset, $key, $value = null,
+ $duplicate = false)
+ {
+ $callback = $this->getOption('tooltip_callback');
+ $structure = $this->getOption('structure');
+ if(is_callable($callback)) {
+ if($value === null)
+ $value = $key;
+ $text = call_user_func_array($callback, [$dataset, $key, $value]);
+ } elseif(is_array($structure) && isset($structure['tooltip'])) {
+ // use structured data tooltips if specified
+ $text = $item->tooltip;
+ } else {
+ $text = $this->formatTooltip($item, $dataset, $key, $value);
+ }
+ if($text === null)
+ return;
+ $text = addslashes(str_replace("\n", '\n', $text));
+ return $this->getJavascript()->setTooltip($element, $text, $duplicate);
+ }
+
+ /**
+ * Default format is value only
+ */
+ protected function formatTooltip(&$item, $dataset, $key, $value)
+ {
+ $n = new Number($value, $this->getOption('units_tooltip'),
+ $this->getOption('units_before_tooltip'));
+ return $n->format();
+ }
+
+ /**
+ * Adds a data label to the list
+ */
+ protected function addDataLabel($dataset, $index, &$element, &$item,
+ $x, $y, $w, $h, $content = null, $duplicate = true)
+ {
+ if(!$this->getOption(['show_data_labels', $dataset]))
+ return false;
+
+ // set up fading for this label?
+ $id = null;
+ $fade_in = $this->getOption(['data_label_fade_in_speed', $dataset]);
+ $fade_out = $this->getOption(['data_label_fade_out_speed', $dataset]);
+ $click = $this->getOption(['data_label_click', $dataset]);
+ $popup = $this->getOption(['data_label_popfront', $dataset]);
+ if($click == 'hide' || $click == 'show') {
+ $id = $this->newID();
+ $this->getJavascript()->setClickShow($element, $id, $click == 'hide', $duplicate);
+ }
+ if($popup) {
+ if(!$id)
+ $id = $this->newID();
+ $this->getJavascript()->setPopFront($element, $id, $duplicate);
+ }
+ if($fade_in || $fade_out) {
+ $speed_in = $fade_in ? $fade_in / 100 : 0;
+ $speed_out = $fade_out ? $fade_out / 100 : 0;
+ if(!$id)
+ $id = $this->newID();
+ $this->getJavascript()->setFader($element, $speed_in, $speed_out, $id, $duplicate);
+ }
+ $this->getDataLabels()->addLabel($dataset, $index, $item, $x, $y, $w, $h, $id,
+ $content, $fade_in, $click);
+ return true;
+ }
+
+ /**
+ * Adds an element as a client of existing label
+ */
+ protected function addLabelClient($dataset, $index, &$element)
+ {
+ $label = $this->getDataLabels()->getLabel($dataset, $index);
+ if($label === null)
+ return false;
+
+ $id = $label['id'];
+ $fade_in = $this->getOption(['data_label_fade_in_speed', $dataset]);
+ $fade_out = $this->getOption(['data_label_fade_out_speed', $dataset]);
+ $click = $this->getOption(['data_label_click', $dataset]);
+ $popup = $this->getOption(['data_label_popfront', $dataset]);
+ if($click == 'hide' || $click == 'show')
+ $this->getJavascript()->setClickShow($element, $id, $click == 'hide', true);
+ if($popup)
+ $this->getJavascript()->setPopFront($element, $id, true);
+ if($fade_in || $fade_out) {
+ $speed_in = $fade_in ? $fade_in / 100 : 0;
+ $speed_out = $fade_out ? $fade_out / 100 : 0;
+ $this->getJavascript()->setFader($element, $speed_in, $speed_out, $id, true);
+ }
+ }
+
+ /**
+ * Adds a label for non-data text
+ */
+ protected function addContentLabel($dataset, $index, $x, $y, $w, $h, $content)
+ {
+ $this->getDataLabels()->addContentLabel($dataset, $index, $x, $y, $w, $h,
+ $content);
+ return true;
+ }
+
+ /**
+ * Draws the data labels
+ */
+ protected function drawDataLabels()
+ {
+ if(isset($this->settings['label']))
+ $this->getDataLabels()->load($this->settings);
+ return $this->getDataLabels()->getLabels();
+ }
+
+ /**
+ * Returns the position for a data label
+ */
+ public function dataLabelPosition($dataset, $index, &$item, $x, $y, $w, $h,
+ $label_w, $label_h)
+ {
+ $pos = $this->getOption(['data_label_position', $dataset]);
+ if(empty($pos))
+ $pos = 'above';
+ $end = [$x + $w * 0.5, $y + $h * 0.5];
+ return [$pos, $end];
+ }
+
+ /**
+ * Returns the ShapeList instance
+ */
+ public function getShapeList()
+ {
+ if($this->shapes === null) {
+ $this->shapes = new ShapeList($this);
+ $this->shapes->load($this->settings);
+ }
+ return $this->shapes;
+ }
+
+ public function underShapes()
+ {
+ if(!isset($this->settings['shape']))
+ return '';
+ return $this->getShapeList()->draw(ShapeList::BELOW);
+ }
+
+ public function overShapes()
+ {
+ if(!isset($this->settings['shape']))
+ return '';
+ return $this->getShapeList()->draw(ShapeList::ABOVE);
+ }
+
+ /**
+ * Returns TRUE if the position is inside the item
+ */
+ public static function isPositionInside($pos)
+ {
+ list($hpos, $vpos) = Graph::translatePosition($pos);
+ return strpos($hpos . $vpos, 'o') === false;
+ }
+
+ /**
+ * Sets the styles for data labels
+ */
+ public function dataLabelStyle($dataset, $index, &$item)
+ {
+ // this function gets called a lot, so cache the return values
+ if(isset($this->data_label_style_cache[$dataset]))
+ return $this->data_label_style_cache[$dataset];
+
+ $map = $this->getDataLabels()->getStyleMap();
+ $style = [];
+ foreach($map as $key => $option) {
+ $style[$key] = $this->getOption([$option, $dataset]);
+ }
+
+ // padding x/y options override single value
+ $style['pad_x'] = $this->getOption(['data_label_padding_x', $dataset],
+ ['data_label_padding', $dataset]);
+ $style['pad_y'] = $this->getOption(['data_label_padding_y', $dataset],
+ ['data_label_padding', $dataset]);
+
+ $this->data_label_style_cache[$dataset] = $style;
+ return $style;
+ }
+
+ /**
+ * Tail direction is required for some types of label
+ */
+ public function dataLabelTailDirection($dataset, $index, $hpos, $vpos)
+ {
+ // angle starts at right, goes clockwise
+ $angle = 90;
+ $pos = str_replace(['i', 'o', 'm'], '', $vpos) .
+ str_replace(['i', 'o', 'c'], '', $hpos);
+ switch($pos) {
+ case 'l' : $angle = 0; break;
+ case 'tl' : $angle = 45; break;
+ case 't' : $angle = 90; break;
+ case 'tr' : $angle = 135; break;
+ case 'r' : $angle = 180; break;
+ case 'br' : $angle = 225; break;
+ case 'b' : $angle = 270; break;
+ case 'bl' : $angle = 315; break;
+ }
+ return $angle;
+ }
+
+ /**
+ * Builds and returns the body of the graph
+ */
+ private function buildGraph()
+ {
+ $this->checkValues($this->values);
+
+ // body content comes from the subclass
+ return $this->drawGraph();
+ }
+
+ /**
+ * Returns the SVG document
+ */
+ public function fetch($header = true, $defer_javascript = true)
+ {
+ $content = '';
+ if($header) {
+ $content .= 'encoding) > 0)
+ $content .= ' encoding="' . $this->encoding . '"';
+ $content .= ' standalone="no"?' . ">\n";
+ if($this->getOption('doctype'))
+ $content .= '' . "\n";
+ }
+
+ // set the precision - PHP default is 14 digits!
+ $old_precision = ini_set('precision', $this->settings['precision']);
+ Number::setup($this->settings['precision'], $this->settings['decimal'],
+ $this->settings['thousands']);
+
+ $heading = $foot = '';
+ // display title and description if available
+ if($this->getOption('title'))
+ $heading .= $this->element('title', null, null, $this->getOption('title'));
+ if($this->getOption('description'))
+ $heading .= $this->element('desc', null, null, $this->getOption('description'));
+
+ try {
+ $body = $this->buildGraph();
+ } catch(\Exception $e) {
+ if($this->getOption('exception_throw'))
+ throw $e;
+
+ $err = $e->getMessage();
+ $details = $this->getOption('exception_details');
+ if($details)
+ $err .= ' [' . basename($e->getFile()) . ' #' . $e->getLine() . ']';
+ $body = $this->errorText($err);
+
+ if($details) {
+ $body .= "\n";
+ }
+ }
+
+ $svg = [
+ 'version' => '1.1',
+ 'width' => new Number($this->width),
+ 'height' => new Number($this->height),
+ ];
+
+ if($this->subgraph) {
+ // subgraphs need x and y, and can overflow
+ $x = $this->getOption('graph_x');
+ $y = $this->getOption('graph_y');
+ if($x)
+ $svg['x'] = $x;
+ if($y)
+ $svg['y'] = $y;
+ $svg['overflow'] = 'visible';
+ } else {
+ if($this->namespace)
+ $svg['xmlns:svg'] = 'http://www.w3.org/2000/svg';
+ else
+ $svg['xmlns'] = 'http://www.w3.org/2000/svg';
+ $svg['xmlns:xlink'] = 'http://www.w3.org/1999/xlink';
+
+ // add any extra namespaces
+ foreach($this->namespaces as $ns => $url)
+ $svg['xmlns:' . $ns] = $url;
+
+ if($this->getOption('auto_fit')) {
+ // convert pixel size to viewbox size
+ $svg['viewBox'] = '0 0 ' . $svg['width'] . ' ' . $svg['height'];
+ $svg['width'] = $svg['height'] = '100%';
+ }
+ }
+ if($this->getOption('svg_class'))
+ $svg['class'] = $this->getOption('svg_class');
+
+ if(!$defer_javascript)
+ $foot .= $this->fetchJavascript(true, !$this->namespace);
+
+ // add defs to heading
+ $heading .= $this->defs->get();
+
+ // display version string
+ if($this->getOption('show_version')) {
+ $text = ['x' => $this->pad_left, 'y' => $this->height - 3];
+ $style = [
+ 'font-family' => 'Courier New',
+ 'font-size' => '12px',
+ 'font-weight' => 'bold',
+ ];
+ $body .= $this->contrastText($text['x'], $text['y'], SVGGraph::VERSION,
+ 'blue', 'white', $style);
+ }
+
+ $content .= $this->element('svg', $svg, null, $heading . $body . $foot);
+ // replace PHP's precision
+ ini_set('precision', $old_precision);
+
+ if($this->getOption('minify'))
+ $content = preg_replace('/\>\s+\', '><', $content);
+ return $content;
+ }
+
+ /**
+ * Renders the SVG document
+ */
+ public function render($header = true, $content_type = true,
+ $defer_javascript = false)
+ {
+ $mime_header = 'Content-type: image/svg+xml; charset=UTF-8';
+ if($content_type)
+ header($mime_header);
+
+ try {
+ echo $this->fetch($header, $defer_javascript);
+ } catch(\Exception $e) {
+ if($this->getOption('exception_throw'))
+ throw $e;
+ $this->errorText($e);
+ }
+ }
+
+ /**
+ * When using the defer_javascript option, this returns the
+ * Javascript block
+ */
+ public function fetchJavascript($cdata = true, $no_namespace = true)
+ {
+ if(!isset(Graph::$javascript))
+ return '';
+
+ $script = Graph::$javascript->getCode($cdata, $this->getOption('minify_js'));
+ if($script == '')
+ return '';
+
+ $script_attr = ['type' => 'application/ecmascript'];
+ $namespace = $this->namespace;
+ if($no_namespace)
+ $this->namespace = false;
+ $js = $this->element('script', $script_attr, null, $script);
+ if($no_namespace)
+ $this->namespace = $namespace;
+ return $js;
+ }
+
+ /**
+ * Returns the minimum value in the array, ignoring NULLs
+ */
+ public static function min(&$a)
+ {
+ $min = null;
+ foreach($a as $v) {
+ if($v !== null && ($min === null || $v < $min))
+ $min = $v;
+ }
+ return $min;
+ }
+
+ /**
+ * Converts a string key to a unix timestamp, or NULL if invalid
+ */
+ public static function dateConvert($k)
+ {
+ // date_create functions return false if the conversion fails
+ $dt = false;
+
+ // if the format is set, try it
+ if(Graph::$key_format !== null)
+ $dt = date_create_from_format(Graph::$key_format, $k);
+
+ // try default conversion
+ if($dt === false)
+ $dt = date_create($k);
+
+ // give up
+ if($dt === false)
+ return null;
+
+ // this works in 64-bit on 32-bit systems, getTimestamp() doesn't
+ return $dt->format('U');
+ }
+}
diff --git a/classes/vendor/81x/goat1000/svggraph/GridGraph.php b/classes/vendor/81x/goat1000/svggraph/GridGraph.php
new file mode 100644
index 0000000..ba2a5c0
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/GridGraph.php
@@ -0,0 +1,1265 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+abstract class GridGraph extends Graph {
+
+ protected $x_axes;
+ protected $y_axes;
+ protected $main_x_axis = 0;
+ protected $main_y_axis = 0;
+ protected $y_axis_positions = [];
+ protected $dataset_axes = null;
+
+ protected $g_width = null;
+ protected $g_height = null;
+ protected $label_adjust_done = false;
+ protected $axes_calc_done = false;
+ protected $grid_calc_done = false;
+ protected $guidelines;
+ protected $min_guide = ['x' => null, 'y' => null];
+ protected $max_guide = ['x' => null, 'y' => null];
+
+ protected $grid_limit;
+ private $grid_clip_id = [];
+
+ public function __construct($w, $h, array $settings, array $fixed_settings = [])
+ {
+ $fs = [
+ // Set to true for block-based labelling
+ 'label_centre' => false,
+ // Set to true for graphs that don't support multiple axes (e.g. stacked)
+ 'single_axis' => false,
+ ];
+ $fs = array_merge($fs, $fixed_settings);
+
+ // deprecated options need converting
+ if(isset($settings['show_label_h']) &&
+ !isset($settings['show_axis_text_h']))
+ $settings['show_axis_text_h'] = $settings['show_label_h'];
+ if(isset($settings['show_label_v']) &&
+ !isset($settings['show_axis_text_v']))
+ $settings['show_axis_text_v'] = $settings['show_label_v'];
+
+ // convert _x and _y labels to _h and _v
+ if(!empty($settings['label_y']) && empty($settings['label_v']))
+ $settings['label_v'] = $settings['label_y'];
+ if(!empty($settings['label_x']) && empty($settings['label_h']))
+ $settings['label_h'] = $settings['label_x'];
+
+ parent::__construct($w, $h, $settings, $fs);
+
+ // set up user text classes file
+ $tcf = $this->getOption('text_classes_file');
+ if(!empty($tcf))
+ TextClass::setFile($tcf);
+ }
+
+ /**
+ * Modifies the graph padding to allow room for labels
+ */
+ protected function labelAdjustment()
+ {
+ $grid_l = $grid_t = $grid_r = $grid_b = null;
+
+ $grid_set = $this->getOption('grid_left', 'grid_right', 'grid_top',
+ 'grid_bottom');
+ if($grid_set) {
+ if(!empty($this->getOption('grid_left')))
+ $grid_l = $this->pad_left = abs($this->getOption('grid_left'));
+ if(!empty($this->getOption('grid_top')))
+ $grid_t = $this->pad_top = abs($this->getOption('grid_top'));
+
+ if(!empty($this->getOption('grid_bottom'))) {
+ $gb = $this->getOption('grid_bottom');
+ $grid_b = $this->pad_bottom = $gb < 0 ? abs($gb) : $this->height - $gb;
+ }
+ if(!empty($this->getOption('grid_right'))) {
+ $gr = $this->getOption('grid_right');
+ $grid_r = $this->pad_right = $gr < 0 ? abs($gr) : $this->width - $gr;
+ }
+ }
+
+ $label_v = $this->getOption('label_v');
+ $label_h = $this->getOption('label_h');
+ if($this->getOption('axis_right') && !empty($label_v) &&
+ $this->yAxisCount() <= 1) {
+ $label = is_array($label_v) ? $label_v[0] : $label_v;
+ $this->setOption('label_v', [0 => '', 1 => $label]);
+ }
+ if($this->getOption('axis_top') && !empty($label_h) &&
+ $this->xAxisCount() <= 1) {
+ $label = is_array($label_h) ? $label_h[0] : $label_h;
+ $this->setOption('label_h', [0 => '', 1 => $label]);
+ }
+
+ $pad_l = $pad_r = $pad_b = $pad_t = 0;
+ $space_x = $this->width - $this->pad_left - $this->pad_right;
+ $space_y = $this->height - $this->pad_top - $this->pad_bottom;
+ $ends = $this->getAxisEnds();
+ $extra_r = $extra_t = 0;
+
+ for($i = 0; $i < 10; ++$i) {
+ // find the text bounding box and add overlap to padding
+ // repeat with the new measurements in case overlap increases
+ $x_len = $space_x - $pad_r - $pad_l;
+ $y_len = $space_y - $pad_t - $pad_b;
+
+ // 3D graphs will use this to reduce axis length
+ list($extra_r, $extra_t) = $this->adjustAxes($x_len, $y_len);
+ list($x_axes, $y_axes) = $this->getAxes($ends, $x_len, $y_len);
+ $bbox = $this->findAxisBBox($x_len, $y_len, $x_axes, $y_axes);
+ $pr = $pl = $pb = $pt = 0;
+
+ if($bbox['max_x'] > $x_len)
+ $pr = ceil($bbox['max_x'] - $x_len);
+ if($bbox['min_x'] < 0)
+ $pl = ceil(abs($bbox['min_x']));
+ if($bbox['min_y'] < 0)
+ $pt = ceil(abs($bbox['min_y']));
+ if($bbox['max_y'] > $y_len)
+ $pb = ceil($bbox['max_y'] - $y_len);
+
+ if($pr == $pad_r && $pl == $pad_l && $pt == $pad_t && $pb == $pad_b)
+ break;
+ $pad_r = $pr;
+ $pad_l = $pl;
+ $pad_t = $pt;
+ $pad_b = $pb;
+ }
+
+ $pad_r += $extra_r;
+ $pad_t += $extra_t;
+
+ // apply the extra padding
+ if($grid_l === null)
+ $this->pad_left += $pad_l;
+ if($grid_b === null)
+ $this->pad_bottom += $pad_b;
+ if($grid_r === null)
+ $this->pad_right += $pad_r;
+ if($grid_t === null)
+ $this->pad_top += $pad_t;
+
+ if($grid_r !== null || $grid_l !== null) {
+ // fixed grid position means recalculating axis positions
+ $offset = 0;
+ foreach($this->y_axis_positions as $axis_no => $pos) {
+ if($axis_no == 0)
+ continue;
+ if($offset) {
+ $newpos = $pos + $offset;
+ } else {
+ $newpos = $this->width - $this->pad_left - $this->pad_right;
+ $offset = $newpos - $pos;
+ }
+ $this->y_axis_positions[$axis_no] = $newpos;
+ }
+ }
+
+ // see if the axes fit
+ foreach($this->y_axis_positions as $axis_no => $pos) {
+ if($axis_no > 0 && $pos <= 0)
+ throw new \Exception('Not enough space for ' . $this->yAxisCount() . ' axes');
+ }
+ $this->label_adjust_done = true;
+ }
+
+ /**
+ * Subclasses can override this to modify axis lengths
+ * Return amount of padding added [r,t]
+ */
+ protected function adjustAxes(&$x_len, &$y_len)
+ {
+ return [0, 0];
+ }
+
+ /**
+ * Find the bounding box of the axis text for given axis lengths
+ */
+ protected function findAxisBBox($length_x, $length_y, $x_axes, $y_axes)
+ {
+ // initialise maxima and minima
+ $min_x = [$this->width];
+ $min_y = [$this->height];
+ $max_x = [0];
+ $max_y = [0];
+
+ $axis_no = -1;
+ foreach($x_axes as $x_axis) {
+ ++$axis_no;
+ if($x_axis === null)
+ continue;
+ $display_axis = $this->getDisplayAxis($x_axis, $axis_no, 'h', 'x');
+ $m = $display_axis->measure();
+ $offset = $axis_no ? 0 : $length_y;
+ $min_x[] = $m->x1;
+ $min_y[] = $m->y1 + $offset;
+ $max_x[] = $m->x2;
+ $max_y[] = $m->y2 + $offset;
+ }
+
+ $axis_no = -1;
+ $right_pos = $length_x;
+ foreach($y_axes as $y_axis) {
+ ++$axis_no;
+ if($y_axis === null)
+ continue;
+ $ybb = $this->yAxisBBox($y_axis, $length_y, $axis_no);
+
+ if($axis_no > 0) {
+ // for offset axes, the inside overlap must be added on too
+ $outer = $ybb['max_x'];
+ $inner = $axis_no > 1 ? abs($ybb['min_x']) : 0;
+
+ $this->y_axis_positions[$axis_no] = $right_pos + $inner;
+ $ybb['max_x'] += $right_pos + $inner;
+ $ybb['min_x'] += $right_pos + $inner;
+ $right_pos += $inner + $outer + $this->getOption('axis_space');
+ } else {
+ $this->y_axis_positions[$axis_no] = 0;
+ }
+
+ $max_x[] = $ybb['max_x'];
+ $min_x[] = $ybb['min_x'];
+ $max_y[] = $ybb['max_y'];
+ $min_y[] = $ybb['min_y'];
+ }
+ return [
+ 'min_x' => min($min_x), 'min_y' => min($min_y),
+ 'max_x' => max($max_x), 'max_y' => max($max_y)
+ ];
+ }
+
+ /**
+ * Returns bounding box for a Y-axis
+ */
+ protected function yAxisBBox($axis, $length, $axis_no)
+ {
+ $display_axis = $this->getDisplayAxis($axis, $axis_no, 'v', 'y');
+ $bbox = $display_axis->measure();
+
+ // reversed Y-axis measures from bottom
+ if($axis->reversed())
+ $bbox->offset(0, $length);
+ return [ 'min_x' => $bbox->x1, 'min_y' => $bbox->y1,
+ 'max_x' => $bbox->x2, 'max_y' => $bbox->y2 ];
+ }
+
+ /**
+ * Sets up grid width and height to fill padded area
+ */
+ protected function setGridDimensions()
+ {
+ $this->g_height = $this->height - $this->pad_top - $this->pad_bottom;
+ $this->g_width = $this->width - $this->pad_left - $this->pad_right;
+ }
+
+ /**
+ * Returns the list of datasets and their axis numbers
+ */
+ public function getDatasetAxes()
+ {
+ if($this->dataset_axes !== null)
+ return $this->dataset_axes;
+
+ $dataset_axis = $this->getOption('dataset_axis');
+ $default_axis = $this->getOption('axis_right') ? 1 : 0;
+ $single_axis = $this->getOption('single_axis');
+ if(empty($this->multi_graph)) {
+ $enabled_datasets = [0];
+ $v =& $this->values;
+ } else {
+ $enabled_datasets = $this->multi_graph->getEnabledDatasets();
+ $v =& $this->multi_graph->getValues();
+ }
+
+ $d_axes = [];
+ foreach($enabled_datasets as $d) {
+ $axis = $default_axis;
+
+ // only use the chosen dataset axis when allowed and not empty
+ if(!$single_axis && isset($dataset_axis[$d]) && $v->itemsCount($d) > 0)
+ $axis = $dataset_axis[$d];
+ $d_axes[$d] = $axis;
+ }
+
+ // check that the axes are used in order
+ $used = [];
+ foreach($d_axes as $a)
+ $used[$a] = 1;
+ $max = max($d_axes);
+ $unused = [];
+ for($a = $default_axis; $a <= $max; ++$a)
+ if(!isset($used[$a]))
+ $unused[] = $a;
+
+ if(count($unused))
+ throw new \Exception('Unused axis: ' . implode(', ', $unused));
+
+ $this->dataset_axes = $d_axes;
+ return $this->dataset_axes;
+ }
+
+ /**
+ * Returns the number of Y-axes
+ */
+ protected function yAxisCount()
+ {
+ $dataset_axes = $this->getDatasetAxes();
+ if(count($dataset_axes) <= 1)
+ return 1;
+
+ return count(array_unique($dataset_axes));
+ }
+
+ /**
+ * Returns the number of X-axes
+ */
+ protected function xAxisCount()
+ {
+ return 1;
+ }
+
+ /**
+ * Returns an x or y axis, or NULL if it does not exist
+ */
+ public function getAxis($axis, $num)
+ {
+ if($num === null)
+ $num = ($axis == 'y' ? $this->main_y_axis : $this->main_x_axis);
+ $axis_var = $axis == 'y' ? 'y_axes' : 'x_axes';
+ if(isset($this->{$axis_var}) && isset($this->{$axis_var}[$num]))
+ return $this->{$axis_var}[$num];
+ return null;
+ }
+
+ /**
+ * Returns the Y-axis for a dataset
+ */
+ protected function datasetYAxis($dataset)
+ {
+ $dataset_axes = $this->getDatasetAxes();
+ if(isset($dataset_axes[$dataset]))
+ return $dataset_axes[$dataset];
+ return $this->getOption('axis_right') ? 1 : 0;
+ }
+
+ /**
+ * Returns the minimum value for an axis
+ */
+ protected function getAxisMinValue($axis)
+ {
+ if($this->yAxisCount() <= 1)
+ return $this->getMinValue();
+
+ $min = [];
+ $dataset_axes = $this->getDatasetAxes();
+ foreach($dataset_axes as $dataset => $d_axis) {
+ if($d_axis == $axis) {
+ $min_val = $this->values->getMinValue($dataset);
+ if($min_val !== null)
+ $min[] = $min_val;
+ }
+ }
+ return empty($min) ? null : min($min);
+ }
+
+ /**
+ * Returns the maximum value for an axis
+ */
+ protected function getAxisMaxValue($axis)
+ {
+ if($this->yAxisCount() <= 1)
+ return $this->getMaxValue();
+
+ $max = [];
+ $dataset_axes = $this->getDatasetAxes();
+ foreach($dataset_axes as $dataset => $d_axis) {
+ if($d_axis == $axis) {
+ $max_val = $this->values->getMaxValue($dataset);
+ if($max_val !== null)
+ $max[] = $max_val;
+ }
+ }
+ return empty($max) ? null : max($max);
+ }
+
+ /**
+ * Returns fixed min and max option for an axis
+ */
+ protected function getFixedAxisOptions($axis, $index)
+ {
+ $a = $axis == 'y' ? 'v' : 'h';
+ $min = $this->getOption(['axis_min_' . $a, $index]);
+ $max = $this->getOption(['axis_max_' . $a, $index]);
+ return [$min, $max];
+ }
+
+ /**
+ * Returns an array containing the value and key axis min and max
+ */
+ protected function getAxisEnds()
+ {
+ // check guides
+ if($this->guidelines === null)
+ $this->calcGuidelines();
+
+ $v_max = $v_min = $k_max = $k_min = [];
+ $g_min_x = $g_min_y = $g_max_x = $g_max_y = null;
+
+ if($this->guidelines !== null) {
+ list($g_min_x, $g_min_y, $g_max_x, $g_max_y) = $this->guidelines->getMinMax();
+ }
+ $y_axis_count = $this->yAxisCount();
+ $x_axis_count = $this->xAxisCount();
+
+ for($i = 0; $i < $y_axis_count; ++$i) {
+ list($fixed_min, $fixed_max) = $this->getFixedAxisOptions('y', $i);
+
+ // validate
+ if(is_numeric($fixed_min) && is_numeric($fixed_max) &&
+ $fixed_max < $fixed_min)
+ throw new \Exception('Invalid Y axis options: min > max (' .
+ $fixed_min . ' > ' . $fixed_max . ')');
+
+ if(is_numeric($fixed_min) && is_numeric($fixed_max)) {
+ $v_min[] = $fixed_min;
+ $v_max[] = $fixed_max;
+ } else {
+ $allow_zero = !$this->getOption(['log_axis_y', $i], false);
+ $prefer_zero = $this->getOption(['axis_zero_y', $i], true);
+ $axis_min_value = $this->getAxisMinValue($i);
+ $axis_max_value = $this->getAxisMaxValue($i);
+ if($g_min_y !== null && $g_min_y < $axis_min_value)
+ $axis_min_value = $g_min_y;
+ if($g_max_y !== null && $g_max_y > $axis_max_value)
+ $axis_max_value = $g_max_y;
+
+ $v_min[] = is_numeric($fixed_min) ? $fixed_min :
+ Axis::calcMinimum($axis_min_value, $axis_max_value,
+ $allow_zero, $prefer_zero);
+
+ $v_max[] = is_numeric($fixed_max) ? $fixed_max :
+ Axis::calcMaximum($axis_min_value, $axis_max_value,
+ $allow_zero, $prefer_zero);
+ }
+ if($v_max[$i] < $v_min[$i])
+ throw new \Exception('Invalid Y axis: min > max (' .
+ $v_min[$i] . ' > ' . $v_max[$i] . ')');
+ }
+
+ for($i = 0; $i < $x_axis_count; ++$i) {
+ list($fixed_min, $fixed_max) = $this->getFixedAxisOptions('x', $i);
+
+ if($this->getOption('datetime_keys')) {
+ // 0 is 1970-01-01, not a useful minimum
+ if(empty($fixed_max)) {
+ // guidelines support datetime values too
+ if($g_max_x !== null)
+ $k_max[] = max($this->getMaxKey(), $g_max_x);
+ else
+ $k_max[] = $this->getMaxKey();
+ } else {
+ $d = Graph::dateConvert($fixed_max);
+ // subtract a sec
+ if($d !== null)
+ $k_max[] = $d - 1;
+ else
+ throw new \Exception('Could not convert [' . $fixed_max .
+ '] to datetime');
+ }
+ if(empty($fixed_min)) {
+ if($g_min_x !== null)
+ $k_min[] = min($this->getMinKey(), $g_min_x);
+ else
+ $k_min[] = $this->getMinKey();
+ } else {
+ $d = Graph::dateConvert($fixed_min);
+ if($d !== null)
+ $k_min[] = $d;
+ else
+ throw new \Exception('Could not convert [' . $fixed_min .
+ '] to datetime');
+ }
+ } else {
+ // validate
+ if(is_numeric($fixed_min) && is_numeric($fixed_max) &&
+ $fixed_max < $fixed_min)
+ throw new \Exception('Invalid X axis options: min > max (' .
+ $fixed_min . ' > ' . $fixed_max . ')');
+
+ $log_axis = $this->getOption(['log_axis_x', $i]);
+ if(is_numeric($fixed_max)) {
+ $k_max[] = $fixed_max;
+ } elseif($log_axis) {
+ $max_val = $this->getMaxKey();
+ if($g_max_x !== null)
+ $max_val = max($max_val, (float)$g_max_x);
+ $k_max[] = $max_val;
+ } else {
+ $k_max[] = max(0, $this->getMaxKey(), (float)$g_max_x);
+ }
+
+ if(is_numeric($fixed_min)) {
+ $k_min[] = $fixed_min;
+ } elseif($log_axis) {
+ $min_val = $this->getMinKey();
+ if($g_min_x !== null)
+ $min_val = min($min_val, (float)$g_min_x);
+ $k_min[] = $min_val;
+ } else {
+ $k_min[] = min(0, $this->getMinKey(), (float)$g_min_x);
+ }
+ }
+ if($k_max[$i] < $k_min[$i])
+ throw new \Exception('Invalid X axis: min > max (' . $k_min[$i] .
+ ' > ' . $k_max[$i] . ')');
+ }
+ return compact('v_max', 'v_min', 'k_max', 'k_min');
+ }
+
+ /**
+ * Returns the factory for an X-axis
+ */
+ protected function getXAxisFactory()
+ {
+ $x_bar = $this->getOption('label_centre');
+ return new AxisFactory($this->getOption('datetime_keys'), $this->settings,
+ true, $x_bar, false);
+ }
+
+ /**
+ * Returns the factory for a Y-axis
+ */
+ protected function getYAxisFactory()
+ {
+ return new AxisFactory(false, $this->settings, false, false, true);
+ }
+
+ protected function createXAxis($factory, $length, $ends, $i, $min_space, $grid_division)
+ {
+ $max_h = $ends['k_max'][$i];
+ $min_h = $ends['k_min'][$i];
+ if(!is_numeric($max_h) || !is_numeric($min_h))
+ throw new \Exception('Non-numeric min/max');
+
+ $min_unit = 1;
+ $units_after = (string)$this->getOption(['units_x', $i]);
+ $units_before = (string)$this->getOption(['units_before_x', $i]);
+ $decimal_digits = $this->getOption(['decimal_digits_x', $i],
+ 'decimal_digits');
+ $text_callback = $this->getOption(['axis_text_callback_x', $i],
+ 'axis_text_callback');
+ $values = $this->multi_graph ? $this->multi_graph : $this->values;
+ $log = $this->getOption(['log_axis_x', $i]);
+ $log_base = $this->getOption(['log_axis_x_base', $i]);
+ $levels = $this->getOption(['axis_levels_h', $i]);
+ $ticks = $this->getOption('axis_ticks_x');
+
+ return $factory->get($length, $min_h, $max_h, $min_unit,
+ $min_space, $grid_division, $units_before, $units_after,
+ $decimal_digits, $text_callback, $values, $log, $log_base,
+ $levels, $ticks);
+ }
+
+ protected function createYAxis($factory, $length, $ends, $i, $min_space, $grid_division)
+ {
+ $max_v = $ends['v_max'][$i];
+ $min_v = $ends['v_min'][$i];
+ if(!is_numeric($max_v) || !is_numeric($min_v))
+ throw new \Exception('Non-numeric min/max');
+
+ $min_unit = $this->getOption(['minimum_units_y', $i]);
+ $text_callback = $this->getOption(['axis_text_callback_y', $i],
+ 'axis_text_callback');
+ $decimal_digits = $this->getOption(['decimal_digits_y', $i],
+ 'decimal_digits');
+ $units_after = (string)$this->getOption(['units_y', $i]);
+ $units_before = (string)$this->getOption(['units_before_y', $i]);
+ $log = $this->getOption(['log_axis_y', $i]);
+ $log_base = $this->getOption(['log_axis_y_base', $i]);
+ $ticks = $this->getOption(['axis_ticks_y', $i]);
+ $values = $levels = null;
+
+ if($min_v == $max_v) {
+ if($min_unit > 0) {
+ $inc = $min_unit;
+ } else {
+ $fallback = $this->getOption('axis_fallback_max');
+ $inc = $fallback > 0 ? $fallback : 1;
+ }
+ $max_v += $inc;
+ }
+
+ $y_axis = $factory->get($length, $min_v, $max_v, $min_unit,
+ $min_space, $grid_division, $units_before, $units_after,
+ $decimal_digits, $text_callback, $values, $log, $log_base,
+ $levels, $ticks);
+ $y_axis->setTightness($this->getOption(['axis_tightness_y', $i]));
+ return $y_axis;
+ }
+
+ /**
+ * Returns the X and Y axis class instances as a list
+ */
+ protected function getAxes($ends, &$x_len, &$y_len)
+ {
+ $this->validateAxisOptions();
+ $x_axis_factory = $this->getXAxisFactory();
+ $y_axis_factory = $this->getYAxisFactory();
+
+ // at the moment there will only be 1 X axis, but allow for expansion
+ $x_axes = [];
+ $x_axis_count = $this->xAxisCount();
+ for($i = 0; $i < $x_axis_count; ++$i) {
+
+ $min_space = $this->getOption(['minimum_grid_spacing_h', $i],
+ 'minimum_grid_spacing');
+ $grid_division = $this->getOption(['grid_division_h', $i]);
+ if(is_numeric($grid_division)) {
+ if($grid_division <= 0)
+ throw new \Exception('Invalid grid division');
+ // if fixed grid spacing is specified, make the min spacing 1 pixel
+ $min_space = 1;
+ $this->setOption('minimum_grid_spacing_h', 1);
+ }
+
+ $x_axes[] = $this->createXAxis($x_axis_factory, $x_len, $ends, $i, $min_space, $grid_division);
+ }
+ // double X axis adds a second axis with same ends as first
+ if($x_axis_count == 1 && $this->getOption('axis_double_x'))
+ $x_axes[] = $this->createXAxis($x_axis_factory, $x_len, $ends, 0, $min_space, $grid_division);
+
+ $y_axes = [];
+ $y_axis_count = $this->yAxisCount();
+ for($i = 0; $i < $y_axis_count; ++$i) {
+
+ $min_space = $this->getOption(['minimum_grid_spacing_v', $i],
+ 'minimum_grid_spacing');
+ // make sure minimum_grid_spacing option is an array
+ if(!is_array($this->getOption('minimum_grid_spacing_v')))
+ $this->setOption('minimum_grid_spacing_v', []);
+
+ $grid_division = $this->getOption(['grid_division_v', $i]);
+ if(is_numeric($grid_division)) {
+ if($grid_division <= 0)
+ throw new \Exception('Invalid grid division');
+ // if fixed grid spacing is specified, make the min spacing 1 pixel
+ $this->setOption('minimum_grid_spacing_v', 1, $i);
+ $min_space = 1;
+ } else {
+ $mgsv = $this->getOption('minimum_grid_spacing_v');
+ if(!isset($mgsv[$i]))
+ $this->setOption('minimum_grid_spacing_v', $min_space, $i);
+ }
+
+ $y_axes[] = $this->createYAxis($y_axis_factory, $y_len, $ends, $i, $min_space, $grid_division);
+ }
+ // double Y axis adds a second axis with same ends as first
+ if($y_axis_count == 1 && $this->getOption('axis_double_y'))
+ $y_axes[] = $this->createYAxis($y_axis_factory, $y_len, $ends, 0, $min_space, $grid_division);
+
+ // set the main axis correctly
+ if($this->getOption('axis_right') && count($y_axes) == 1) {
+ $this->main_y_axis = 1;
+ array_unshift($y_axes, null);
+ }
+ if($this->getOption('axis_top') && count($x_axes) == 1) {
+ $this->main_x_axis = 1;
+ array_unshift($x_axes, null);
+ }
+ return [$x_axes, $y_axes];
+ }
+
+ /**
+ * Axis options can be complicated
+ */
+ protected function validateAxisOptions()
+ {
+ // disable units for associative keys
+ if($this->values->associativeKeys()) {
+ $this->setOption('units_x', null);
+ $this->setOption('units_before_x', null);
+ }
+
+ // ticks are a bit tricky, could be an array or array of arrays or ...
+ $ticks = $this->getOption('axis_ticks_y');
+ if(is_array($ticks)) {
+ $count = count($ticks);
+ $nulls = $arrays = 0;
+ foreach($ticks as $t) {
+ if(is_array($t))
+ ++$arrays;
+ elseif($t === null)
+ ++$nulls;
+ }
+
+ // if array of nulls, null it
+ if($nulls == $count)
+ $this->setOption('axis_ticks_y', null);
+ // if single array, enclose it
+ elseif($arrays == 0)
+ $this->setOption('axis_ticks_y', [$ticks]);
+ }
+ }
+
+ /**
+ * Calculates the effect of axes, applying to padding
+ */
+ protected function calcAxes()
+ {
+ if($this->axes_calc_done)
+ return;
+
+ // can't have multiple invisible axes
+ if(!$this->getOption('show_axes'))
+ $this->setOption('dataset_axis', null);
+
+ $ends = $this->getAxisEnds();
+ if(!$this->label_adjust_done)
+ $this->labelAdjustment();
+ if($this->g_height === null || $this->g_width === null)
+ $this->setGridDimensions();
+
+ list($x_axes, $y_axes) = $this->getAxes($ends, $this->g_width,
+ $this->g_height);
+
+ $this->x_axes = $x_axes;
+ $this->y_axes = $y_axes;
+ $this->axes_calc_done = true;
+ }
+
+ /**
+ * Calculates the position of grid lines
+ */
+ protected function calcGrid()
+ {
+ if($this->grid_calc_done)
+ return;
+
+ $y_axis = $this->y_axes[$this->main_y_axis];
+ $x_axis = $this->x_axes[$this->main_x_axis];
+ $y_axis->getGridPoints(null);
+ $x_axis->getGridPoints(null);
+
+ $this->grid_limit = 0.01 + $this->g_width;
+ if($this->getOption('label_centre'))
+ $this->grid_limit -= $x_axis->unit() / 2;
+ $this->grid_calc_done = true;
+ }
+
+ /**
+ * Returns the grid points for a Y-axis
+ */
+ protected function getGridPointsY($axis)
+ {
+ $a = $this->y_axes[$axis];
+ $offset = $a->reversed() ? $this->height - $this->pad_bottom : $this->pad_top;
+ return $a->getGridPoints($offset);
+ }
+
+ /**
+ * Returns the grid points for an X-axis
+ */
+ protected function getGridPointsX($axis)
+ {
+ $a = $this->x_axes[$axis];
+ $offset = $a->reversed() ? $this->width - $this->pad_right : $this->pad_left;
+ return $a->getGridPoints($offset);
+ }
+
+ /**
+ * Returns the subdivisions for a Y-axis
+ */
+ protected function getSubDivsY($axis)
+ {
+ $a = $this->y_axes[$axis];
+ $offset = $a->reversed() ? $this->height - $this->pad_bottom : $this->pad_top;
+ return $a->getGridSubdivisions($this->getOption('minimum_subdivision'),
+ $this->getOption(['minimum_units_y', $axis]), $offset,
+ $this->getOption(['subdivision_v', $axis]));
+ }
+
+ /**
+ * Returns the subdivisions for an X-axis
+ */
+ protected function getSubDivsX($axis)
+ {
+ $a = $this->x_axes[$axis];
+ $offset = $a->reversed() ? $this->width - $this->pad_right : $this->pad_left;
+ return $a->getGridSubdivisions($this->getOption('minimum_subdivision'), 1,
+ $offset, $this->getOption(['subdivision_h', $axis]));
+ }
+
+ /**
+ * A function to return the DisplayAxis - subclasses should override
+ * to return a different axis type
+ */
+ protected function getDisplayAxis($axis, $axis_no, $orientation, $type)
+ {
+ $var = 'main_' . $type . '_axis';
+ $main = ($axis_no == $this->{$var});
+ $levels = $this->getOption(['axis_levels_' . $orientation, $axis_no]);
+ $class = 'Goat1000\SVGGraph\DisplayAxis';
+ if(is_numeric($levels) && $levels > 1)
+ $class = 'Goat1000\SVGGraph\DisplayAxisLevels';
+
+ return new $class($this, $axis, $axis_no, $orientation, $type, $main,
+ $this->getOption('label_centre'));
+ }
+
+ /**
+ * Returns the location of an axis
+ */
+ protected function getAxisLocation($orientation, $axis_no)
+ {
+ $x = $this->pad_left;
+ $y = $this->pad_top + $this->g_height;
+ if($orientation == 'h') {
+ if($axis_no == 1) {
+ // top axis
+ $y = $this->pad_top;
+ } else {
+ $y0 = $this->y_axes[$this->main_y_axis]->zero();
+ if($this->getOption('show_axis_h') && $y0 >= 0 && $y0 <= $this->g_height)
+ $y -= $y0;
+ }
+ } else {
+ if($axis_no == 0) {
+ $x0 = $this->x_axes[$this->main_x_axis]->zero();
+ if($x0 >= 1 && $x0 < $this->g_width)
+ $x += $x0;
+ } else {
+ $x += $this->y_axis_positions[$axis_no];
+ }
+ // Y axis is normally reversed
+ if(!$this->y_axes[$axis_no]->reversed())
+ $y = $this->pad_top;
+ }
+ return [$x, $y];
+ }
+
+ /**
+ * Draws bar or line graph axes
+ */
+ protected function axes()
+ {
+ $this->calcGrid();
+ $axes = $label_group = $axis_text = '';
+
+ foreach($this->x_axes as $axis_no => $axis) {
+ if($axis !== null) {
+ $display_axis = $this->getDisplayAxis($axis, $axis_no, 'h', 'x');
+ list($x, $y) = $this->getAxisLocation('h', $axis_no);
+ $axes .= $display_axis->draw($x, $y, $this->pad_left, $this->pad_top,
+ $this->g_width, $this->g_height);
+ }
+ }
+ foreach($this->y_axes as $axis_no => $axis) {
+ if($axis !== null) {
+ $display_axis = $this->getDisplayAxis($axis, $axis_no, 'v', 'y');
+ list($x, $y) = $this->getAxisLocation('v', $axis_no);
+ $axes .= $display_axis->draw($x, $y, $this->pad_left, $this->pad_top,
+ $this->g_width, $this->g_height);
+ }
+ }
+
+ return $axes;
+ }
+
+ /**
+ * Returns a set of gridlines
+ */
+ protected function gridLines($path, $colour, $dash, $width, $more = null)
+ {
+ if($path->isEmpty() || $colour == 'none')
+ return '';
+ $opts = ['d' => $path, 'stroke' => new Colour($this, $colour)];
+ if(!empty($dash))
+ $opts['stroke-dasharray'] = $dash;
+ if($width && $width != 1)
+ $opts['stroke-width'] = $width;
+ if(is_array($more))
+ $opts = array_merge($opts, $more);
+ return $this->element('path', $opts);
+ }
+
+ /**
+ * Returns the SVG fragment for grid background stripes
+ */
+ protected function getGridStripes()
+ {
+ if(!$this->getOption('grid_back_stripe'))
+ return '';
+
+ // use array of colours if available, otherwise stripe a single colour
+ $colours = $this->getOption('grid_back_stripe_colour');
+ if(!is_array($colours))
+ $colours = [null, $colours];
+ $opacity = $this->getOption('grid_back_stripe_opacity');
+
+ $bars = '';
+ $c = 0;
+ $num_colours = count($colours);
+ $rect = [
+ 'x' => new Number($this->pad_left),
+ 'width' => new Number($this->g_width),
+ ];
+ if($opacity != 1)
+ $rect['fill-opacity'] = $opacity;
+ $points = $this->getGridPointsY($this->main_y_axis);
+ $first = array_shift($points);
+ $last_pos = $first->position;
+ foreach($points as $grid_point) {
+ $cc = $colours[$c % $num_colours];
+ if($cc !== null) {
+ $rect['y'] = $grid_point->position;
+ $rect['height'] = $last_pos - $grid_point->position;
+ $rect['fill'] = new Colour($this, $cc);
+ $bars .= $this->element('rect', $rect);
+ }
+ $last_pos = $grid_point->position;
+ ++$c;
+ }
+ return $this->element('g', [], null, $bars);
+ }
+
+ /**
+ * Returns the crosshairs code
+ */
+ protected function getCrossHairs()
+ {
+ if(!$this->getOption('crosshairs'))
+ return '';
+
+ $ch = new CrossHairs($this, $this->pad_left, $this->pad_top,
+ $this->g_width, $this->g_height, $this->x_axes[$this->main_x_axis],
+ $this->y_axes[$this->main_y_axis], $this->values->associativeKeys(),
+ false, $this->encoding);
+
+ if($ch->enabled())
+ return $ch->getCrossHairs();
+ return '';
+ }
+
+ /**
+ * Draws the grid behind the bar / line graph
+ */
+ protected function grid()
+ {
+ $this->calcAxes();
+ $this->calcGrid();
+
+ // these are used quite a bit, so convert to Number now
+ $left_num = new Number($this->pad_left);
+ $top_num = new Number($this->pad_top);
+ $width_num = new Number($this->g_width);
+ $height_num = new Number($this->g_height);
+
+ $back = $subpath = '';
+ $grid_group = ['class' => 'grid'];
+ $crosshairs = $this->getCrossHairs();
+
+ // if the grid is not displayed, stop now
+ if(!$this->getOption('show_grid') ||
+ (!$this->getOption('show_grid_h') && !$this->getOption('show_grid_v')))
+ return empty($crosshairs) ? '' :
+ $this->element('g', $grid_group, null, $crosshairs);
+
+ $back_colour = new Colour($this, $this->getOption('grid_back_colour'));
+ if(!$back_colour->isNone()) {
+ $rect = [
+ 'x' => $left_num, 'y' => $top_num,
+ 'width' => $width_num, 'height' => $height_num,
+ 'fill' => $back_colour
+ ];
+ if($this->getOption('grid_back_opacity') != 1)
+ $rect['fill-opacity'] = $this->getOption('grid_back_opacity');
+ $back = $this->element('rect', $rect);
+ }
+ $back .= $this->getGridStripes();
+
+ if($this->getOption('show_grid_subdivisions')) {
+ $subpath_h = new PathData();
+ $subpath_v = new PathData();
+ if($this->getOption('show_grid_h')) {
+ $subdivs = $this->getSubDivsY($this->main_y_axis);
+ foreach($subdivs as $y)
+ $subpath_v->add('M', $left_num, $y->position, 'h', $width_num);
+ }
+ if($this->getOption('show_grid_v')){
+ $subdivs = $this->getSubDivsX($this->main_x_axis);
+ foreach($subdivs as $x)
+ $subpath_h->add('M', $x->position, $top_num, 'v', $height_num);
+ }
+
+ if(!($subpath_h->isEmpty() && $subpath_v->isEmpty())) {
+ $colour_h = $this->getOption('grid_subdivision_colour_h',
+ 'grid_subdivision_colour', 'grid_colour_h', 'grid_colour');
+ $colour_v = $this->getOption('grid_subdivision_colour_v',
+ 'grid_subdivision_colour', 'grid_colour_v', 'grid_colour');
+ $dash_h = $this->getOption('grid_subdivision_dash_h',
+ 'grid_subdivision_dash', 'grid_dash_h', 'grid_dash');
+ $dash_v = $this->getOption('grid_subdivision_dash_v',
+ 'grid_subdivision_dash', 'grid_dash_v', 'grid_dash');
+ $width_h = $this->getOption('grid_subdivision_stroke_width_h',
+ 'grid_subdivision_stroke_width', 'grid_stroke_width_h',
+ 'grid_stroke_width');
+ $width_v = $this->getOption('grid_subdivision_stroke_width_v',
+ 'grid_subdivision_stroke_width', 'grid_stroke_width_v',
+ 'grid_stroke_width');
+
+ if($dash_h == $dash_v && $colour_h == $colour_v && $width_h == $width_v) {
+ $subpath_h->add($subpath_v);
+ $subpath = $this->gridLines($subpath_h, $colour_h, $dash_h, $width_h);
+ } else {
+ $subpath = $this->gridLines($subpath_h, $colour_h, $dash_h, $width_h) .
+ $this->gridLines($subpath_v, $colour_v, $dash_v, $width_v);
+ }
+ }
+ }
+
+ $path_v = new PathData;
+ $path_h = new PathData;
+ if($this->getOption('show_grid_h')) {
+ $points = $this->getGridPointsY($this->main_y_axis);
+ foreach($points as $y)
+ $path_v->add('M', $left_num, $y->position, 'h', $width_num);
+ }
+ if($this->getOption('show_grid_v')) {
+ $points = $this->getGridPointsX($this->main_x_axis);
+ foreach($points as $x)
+ $path_h->add('M', $x->position, $top_num, 'v', $height_num);
+ }
+
+ $colour_h = $this->getOption('grid_colour_h', 'grid_colour');
+ $colour_v = $this->getOption('grid_colour_v', 'grid_colour');
+ $dash_h = $this->getOption('grid_dash_h', 'grid_dash');
+ $dash_v = $this->getOption('grid_dash_v', 'grid_dash');
+ $width_h = $this->getOption('grid_stroke_width_h', 'grid_stroke_width');
+ $width_v = $this->getOption('grid_stroke_width_v', 'grid_stroke_width');
+
+ if($dash_h == $dash_v && $colour_h == $colour_v && $width_h == $width_v) {
+ $path_v->add($path_h);
+ $path = $this->gridLines($path_v, $colour_h, $dash_h, $width_h);
+ } else {
+ $path = $this->gridLines($path_h, $colour_h, $dash_h,$width_h) .
+ $this->gridLines($path_v, $colour_v, $dash_v, $width_v);
+ }
+
+ return $this->element('g', $grid_group, null,
+ $back . $subpath . $path . $crosshairs);
+ }
+
+ /**
+ * clamps a value to the grid boundaries
+ */
+ protected function clampVertical($val)
+ {
+ return max($this->pad_top, min($this->height - $this->pad_bottom, $val));
+ }
+
+ protected function clampHorizontal($val)
+ {
+ return max($this->pad_left, min($this->width - $this->pad_right, $val));
+ }
+
+ /**
+ * Sets the clipping path for the grid
+ */
+ protected function clipGrid(&$attr)
+ {
+ $cx = $this->getOption('grid_clip_overlap_x');
+ $cy = $this->getOption('grid_clip_overlap_y');
+
+ $clip_id = $this->gridClipPath($cx, $cy);
+ $attr['clip-path'] = 'url(#' . $clip_id . ')';
+ }
+
+ /**
+ * Returns the ID of the grid clipping path
+ */
+ public function gridClipPath($x_overlap = 0, $y_overlap = 0)
+ {
+ $cid = new Number($x_overlap) . '_' . new Number($y_overlap);
+ if(isset($this->grid_clip_id[$cid]))
+ return $this->grid_clip_id[$cid];
+
+ $crop_top = max(0, $this->pad_top - $y_overlap);
+ $crop_bottom = min($this->height, $this->height - $this->pad_bottom + $y_overlap);
+ $crop_left = max(0, $this->pad_left - $x_overlap);
+ $crop_right = min($this->width, $this->width - $this->pad_right + $x_overlap);
+ $rect = [
+ 'x' => $crop_left, 'y' => $crop_top,
+ 'width' => $crop_right - $crop_left,
+ 'height' => $crop_bottom - $crop_top,
+ ];
+ $clip_id = $this->newID();
+ $this->defs->add($this->element('clipPath', ['id' => $clip_id], null,
+ $this->element('rect', $rect)));
+ return ($this->grid_clip_id[$cid] = $clip_id);
+ }
+
+ /**
+ * Returns the grid position for a bar or point, or NULL if not on grid
+ * $item = data item
+ * $index = integer position in array
+ */
+ protected function gridPosition($item, $index)
+ {
+ $offset = $this->x_axes[$this->main_x_axis]->position($index, $item);
+ $zero = -0.01; // catch values close to 0
+
+ if($offset >= $zero && floor($offset) <= $this->grid_limit)
+ return $this->pad_left + $offset;
+ return null;
+ }
+
+ /**
+ * Returns an X unit value as a SVG distance
+ */
+ public function unitsX($x, $axis_no = null)
+ {
+ if($axis_no === null)
+ $axis_no = $this->main_x_axis;
+ if(!isset($this->x_axes[$axis_no]))
+ throw new \Exception('Axis x' . $axis_no . ' does not exist');
+ if($this->x_axes[$axis_no] === null)
+ $axis_no = $this->main_x_axis;
+ $axis = $this->x_axes[$axis_no];
+ return $axis->position($x);
+ }
+
+ /**
+ * Returns a Y unit value as a SVG distance
+ */
+ public function unitsY($y, $axis_no = null)
+ {
+ if($axis_no === null)
+ $axis_no = $this->main_y_axis;
+ if(!isset($this->y_axes[$axis_no]))
+ throw new \Exception('Axis y' . $axis_no . ' does not exist');
+ if($this->y_axes[$axis_no] === null)
+ $axis_no = $this->main_y_axis;
+ $axis = $this->y_axes[$axis_no];
+ return $axis->position($y);
+ }
+
+ /**
+ * Returns the $x value as a grid position
+ */
+ public function gridX($x, $axis_no = null)
+ {
+ $p = $this->unitsX($x, $axis_no);
+ if($p !== null)
+ return $this->pad_left + $p;
+ return null;
+ }
+
+ /**
+ * Returns the $y value as a grid position
+ */
+ public function gridY($y, $axis_no = null)
+ {
+ $p = $this->unitsY($y, $axis_no);
+ if($p === null)
+ return null;
+ if($axis_no === null || $this->y_axes[$axis_no] === null)
+ $axis_no = $this->main_y_axis;
+ $axis = $this->y_axes[$axis_no];
+ return $axis->reversed() ? $this->height - $this->pad_bottom - $p :
+ $this->pad_top + $p;
+ }
+
+ /**
+ * Returns the location of the X axis origin
+ */
+ protected function originX($axis_no = null)
+ {
+ if($axis_no === null || $this->x_axes[$axis_no] === null)
+ $axis_no = $this->main_x_axis;
+ $axis = $this->x_axes[$axis_no];
+ return $this->pad_left + $axis->origin();
+ }
+
+ /**
+ * Returns the location of the Y axis origin
+ */
+ protected function originY($axis_no = null)
+ {
+ if($axis_no === null || $this->y_axes[$axis_no] === null)
+ $axis_no = $this->main_y_axis;
+ $axis = $this->y_axes[$axis_no];
+ return $axis->reversed() ?
+ $this->height - $this->pad_bottom - $axis->origin() :
+ $this->pad_top + $axis->origin();
+ }
+
+ /**
+ * Calculates the averages, storing them as guidelines
+ */
+ protected function calcAverages($cls = 'Goat1000\SVGGraph\Average')
+ {
+ $show = $this->getOption('show_average');
+ if(empty($show))
+ return;
+
+ $datasets = empty($this->multi_graph) ? [0] :
+ $this->multi_graph->getEnabledDatasets();
+
+ $average = new $cls($this, $this->values, $datasets);
+ $average->getGuidelines();
+ }
+
+ /**
+ * Loads the guidelines from options
+ */
+ protected function calcGuidelines()
+ {
+ $this->calcAverages();
+ $guidelines = $this->getOption('guideline');
+ if(empty($guidelines) && $guidelines !== 0)
+ return;
+
+ $this->guidelines = new Guidelines($this, false,
+ $this->values->associativeKeys(),
+ $this->getOption('datetime_keys'));
+ }
+
+ public function underShapes()
+ {
+ $content = parent::underShapes();
+ if($this->guidelines !== null)
+ $content .= $this->guidelines->getBelow();
+ return $content;
+ }
+
+ public function overShapes()
+ {
+ $content = parent::overShapes();
+ if($this->guidelines !== null)
+ $content .= $this->guidelines->getAbove();
+ return $content;
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/GridPoint.php b/classes/vendor/81x/goat1000/svggraph/GridPoint.php
new file mode 100644
index 0000000..6a1f333
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/GridPoint.php
@@ -0,0 +1,80 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * Class for axis grid points
+ */
+class GridPoint {
+
+ public $position;
+ public $value;
+ public $item = null;
+ protected $text = [];
+
+ public function __construct($position, $text, $value, $item = null)
+ {
+ $this->position = $position;
+ $this->value = $value;
+ $this->item = $item;
+
+ if(!is_array($text))
+ $text = [(string)$text];
+ foreach($text as $t)
+ $this->text[] = (string)$t;
+ }
+
+ /**
+ * Returns the grid point text for an axis level
+ */
+ public function getText($level = 0)
+ {
+ return isset($this->text[$level]) ? $this->text[$level] : '';
+ }
+
+ /**
+ * Returns true when the text is empty
+ */
+ public function blank($level = 0)
+ {
+ return !isset($this->text[$level]) || $this->text[$level] == '';
+ }
+
+ /**
+ * Returns a value from the item, or NULL
+ */
+ public function __get($field)
+ {
+ if($this->item === null)
+ return null;
+ if(isset($this->item->$field))
+ return $this->item->$field;
+
+ if($this->item->axis_text_class)
+ {
+ $tc = new TextClass($this->item->axis_text_class, 'axis_text_');
+ return $tc->$field;
+ }
+ return null;
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/Grouped3DGraphTrait.php b/classes/vendor/81x/goat1000/svggraph/Grouped3DGraphTrait.php
new file mode 100644
index 0000000..4f2f00a
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/Grouped3DGraphTrait.php
@@ -0,0 +1,51 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+trait Grouped3DGraphTrait {
+
+ use GroupedBarTrait;
+
+ /**
+ * Override AdjustAxes to change depth
+ */
+ protected function adjustAxes(&$x_len, &$y_len)
+ {
+ /**
+ * The depth is roughly 1/$num - but it must also take into account the
+ * bar and group spacing, which is where things get messy
+ */
+ $ends = $this->getAxisEnds();
+ $num = $ends['k_max'][0] - $ends['k_min'][0] + 1;
+
+ $block = $x_len / $num;
+ $datasets = $this->multi_graph->getEnabledDatasets();
+ $group = count($datasets);
+ $a = $this->getOption('bar_space');
+ $b = $this->getOption('group_space');
+ $c = (($block) - $a - ($group - 1) * $b) / $group;
+ $d = ($a + $c) / $block;
+ $this->depth = $d;
+ return parent::adjustAxes($x_len, $y_len);
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/GroupedBar3DGraph.php b/classes/vendor/81x/goat1000/svggraph/GroupedBar3DGraph.php
new file mode 100644
index 0000000..171ed72
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/GroupedBar3DGraph.php
@@ -0,0 +1,29 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class GroupedBar3DGraph extends Bar3DGraph {
+
+ use Grouped3DGraphTrait;
+
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/GroupedBarGraph.php b/classes/vendor/81x/goat1000/svggraph/GroupedBarGraph.php
new file mode 100644
index 0000000..771e5ab
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/GroupedBarGraph.php
@@ -0,0 +1,29 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class GroupedBarGraph extends BarGraph {
+
+ use GroupedBarTrait;
+
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/GroupedBarTrait.php b/classes/vendor/81x/goat1000/svggraph/GroupedBarTrait.php
new file mode 100644
index 0000000..6460c24
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/GroupedBarTrait.php
@@ -0,0 +1,155 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+trait GroupedBarTrait {
+
+ use MultiGraphTrait;
+
+ protected $group_bar_spacing;
+ protected $dataset_offsets = [];
+
+ /**
+ * Draws the bars
+ */
+ protected function drawBars()
+ {
+ $this->barSetup();
+ $dataset_count = count($this->multi_graph);
+ $datasets = $this->multi_graph->getEnabledDatasets();
+
+ // bars must be drawn from left to right, since they might overlap
+ $bars = '';
+ $legend_entries = [];
+ foreach($this->multi_graph as $bnum => $itemlist) {
+ $item = $itemlist[0];
+ $bar_pos = $this->gridPosition($item, $bnum);
+ if($bar_pos !== null) {
+ for($j = 0; $j < $dataset_count; ++$j) {
+ if(!in_array($j, $datasets))
+ continue;
+ $item = $itemlist[$j];
+ $bars .= $this->drawBar($item, $bnum, 0, $this->datasetYAxis($j), $j);
+ $legend_entries[$j][$bnum] = $item;
+ }
+ }
+ }
+
+ // legend entries are added in order of dataset
+ foreach($legend_entries as $j => $dataset)
+ foreach($dataset as $bnum => $item)
+ $this->setBarLegendEntry($j, $bnum, $item);
+
+ return $bars;
+ }
+
+ /**
+ * Sets up bar details
+ */
+ protected function barSetup()
+ {
+ parent::barSetup();
+ $datasets = $this->multi_graph->getEnabledDatasets();
+ $dataset_count = count($datasets);
+
+ list($chunk_width, $bspace, $chunk_unit_width) =
+ $this->barPosition($this->getOption('bar_width'), $this->getOption('bar_width_min'),
+ $this->x_axes[$this->main_x_axis]->unit(), $dataset_count,
+ $this->getOption('bar_space'), $this->getOption('group_space'));
+ $this->group_bar_spacing = $chunk_unit_width;
+ $this->setBarWidth($chunk_width, $bspace);
+
+ $offset = 0;
+ foreach($datasets as $d) {
+ $this->dataset_offsets[$d] = $offset;
+ ++$offset;
+ }
+ }
+
+ /**
+ * Fills in the x and width of bar
+ */
+ protected function barX($item, $index, &$bar, $axis, $dataset)
+ {
+ $bar_x = $this->gridPosition($item, $index);
+ if($bar_x === null)
+ return null;
+
+ $d_offset = $this->dataset_offsets[$dataset];
+ $bar['x'] = $bar_x + $this->calculated_bar_space +
+ ($d_offset * $this->group_bar_spacing);
+ $bar['width'] = $this->calculated_bar_width;
+ return $bar_x;
+ }
+
+ /**
+ * Calculates the bar width, gap to first bar, gap between bars
+ * returns an array containing all three
+ */
+ static function barPosition($bar_width, $bar_width_min, $unit_width,
+ $group_size, $bar_space, $group_space)
+ {
+ if(is_numeric($bar_width) && $bar_width >= 1) {
+ return self::barPositionFixed($bar_width, $unit_width,
+ $group_size, $group_space);
+ } else {
+ // bar width dependent on space
+ $gap_count = $group_size - 1;
+ $gap = $gap_count > 0 ? $group_space : 0;
+
+ $bar_width = $bar_space >= $unit_width ? '1' : $unit_width - $bar_space;
+ if($gap_count > 0 && $gap * $gap_count > $bar_width - $group_size)
+ $gap = ($bar_width - $group_size) / $gap_count;
+ $bar_width = ($bar_width - ($gap * ($group_size - 1)))
+ / $group_size;
+
+ if($bar_width < $bar_width_min)
+ return self::barPositionFixed($bar_width_min, $unit_width,
+ $group_size, $group_space);
+ $spacing = $bar_width + $gap;
+ $offset = $bar_space / 2;
+ }
+ return [$bar_width, $offset, $spacing];
+ }
+
+ /**
+ * Calculate bar width, gaps, using fixed bar width
+ */
+ static function barPositionFixed($bar_width, $unit_width, $group_size,
+ $group_space)
+ {
+ $gap = $group_size > 1 ? $group_space : 0;
+ if($group_size > 1 && ($bar_width + $gap) * $group_size > $unit_width) {
+
+ // bars don't fit with group_space option, so they must overlap
+ // (and make sure the bars are at least 1 pixel apart)
+ $spacing = max(1, ($unit_width - $bar_width) / ($group_size - 1));
+ $offset = 0;
+ } else {
+ // space the bars group_space apart, centred in unit space
+ $spacing = $bar_width + $gap;
+ $offset = max(0, ($unit_width - ($spacing * $group_size)) / 2);
+ }
+ return [$bar_width, $offset, $spacing];
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/GroupedCylinderGraph.php b/classes/vendor/81x/goat1000/svggraph/GroupedCylinderGraph.php
new file mode 100644
index 0000000..b4e0da0
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/GroupedCylinderGraph.php
@@ -0,0 +1,29 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class GroupedCylinderGraph extends CylinderGraph {
+
+ use Grouped3DGraphTrait;
+
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/Guidelines.php b/classes/vendor/81x/goat1000/svggraph/Guidelines.php
new file mode 100644
index 0000000..c580b83
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/Guidelines.php
@@ -0,0 +1,454 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class Guidelines {
+
+ const ABOVE = 1;
+ const BELOW = 0;
+
+ protected $graph;
+ protected $flip_axes;
+ protected $assoc_keys;
+ protected $datetime_keys;
+ protected $guidelines;
+ protected $coords;
+ protected $min_guide = ['x' => null, 'y' => null];
+ protected $max_guide = ['x' => null, 'y' => null];
+
+ private $above;
+ private $colour;
+ private $dash;
+ private $font;
+ private $font_adjust;
+ private $font_size;
+ private $font_weight;
+ private $length;
+ private $length_units;
+ private $opacity;
+ private $stroke_width;
+ private $text_align;
+ private $text_angle;
+ private $text_colour;
+ private $text_opacity;
+ private $text_padding;
+ private $text_position;
+ private $line_spacing;
+
+ public function __construct(&$graph, $flip_axes, $assoc, $datetime)
+ {
+ // see if there is anything to do
+ $lines = $graph->getOption('guideline');
+ if(empty($lines) && $lines !== 0)
+ return;
+
+ $this->graph =& $graph;
+ $this->flip_axes = $flip_axes;
+ $this->assoc_keys = $assoc;
+ $this->datetime_keys = $datetime;
+ $this->guidelines = [];
+
+ // set up options
+ $opts = ['above', 'dash', 'font', 'font_adjust', 'font_weight',
+ 'length', 'length_units', 'opacity', 'stroke_width',
+ 'text_align', 'text_angle', 'text_padding', 'text_position' ];
+ foreach($opts as $opt)
+ $this->{$opt} = $graph->getOption('guideline_' . $opt);
+
+ // more complicated options
+ $this->colour = new Colour($graph, $graph->getOption('guideline_colour'));
+ $this->text_colour = new Colour($graph,
+ $graph->getOption('guideline_text_colour', 'guideline_colour'));
+ $this->text_opacity = $graph->getOption('guideline_text_opacity',
+ 'guideline_opacity');
+ $this->font_size = Number::units($graph->getOption('guideline_font_size'));
+ $this->line_spacing = Number::units($graph->getOption('guideline_line_spacing'));
+
+ $lines = $this->normalize($lines);
+ foreach($lines as $line)
+ $this->calculate($line);
+
+ if(!empty($this->guidelines))
+ $this->coords = new Coords($graph);
+ }
+
+ /**
+ * Simplifies the supported option formats
+ */
+ public static function normalize($lines)
+ {
+ // no lines at all
+ if(empty($lines) && $lines !== 0)
+ return [];
+
+ if(is_array($lines) &&
+ (is_array($lines[0]) || (count($lines) > 1 && !is_string($lines[1])))) {
+
+ // array of guidelines, corrent format
+ return $lines;
+ }
+
+ // single guideline
+ return [$lines];
+ }
+
+ /**
+ * Converts guideline options to more useful member variables
+ */
+ protected function calculate($g)
+ {
+ if(!is_array($g))
+ $g = [$g];
+
+ // $mmvalue is for min/max
+ $value = $mmvalue = $g[0];
+ $axis = (isset($g[2]) && ($g[2] == 'x' || $g[2] == 'y')) ? $g[2] : 'y';
+ if($axis == 'x') {
+ if($this->datetime_keys) {
+ // $value is a datetime string, try to convert it
+ $mmvalue = Graph::dateConvert($value);
+
+ // if the value could not be converted it can't be drawn either
+ if($mmvalue === null)
+ return;
+ } else if($this->assoc_keys) {
+ // $value is a key - must be converted later when the axis
+ // has been created
+ }
+ }
+ $above = isset($g['above']) ? $g['above'] : $this->above;
+ $position = $above ? Guidelines::ABOVE : Guidelines::BELOW;
+ $guideline = [
+ 'value' => $value,
+ 'depth' => $position,
+ 'title' => isset($g[1]) ? $g[1] : '',
+ 'axis' => $axis
+ ];
+ $lopts = $topts = [];
+ $line_opts = [
+ 'dash' => 'stroke-dasharray',
+ 'stroke_width' => 'stroke-width',
+ 'opacity' => 'opacity',
+
+ // not SVG attributes
+ 'length' => 'length',
+ 'length_units' => 'length_units',
+ ];
+ $text_opts = [
+ 'opacity' => 'opacity',
+ 'font' => 'font-family',
+ 'font_weight' => 'font-weight',
+ 'text_opacity' => 'opacity', // overrides line opacity
+
+ // these options do not map to SVG attributes
+ 'font_adjust' => 'font_adjust',
+ 'text_position' => 'text_position',
+ 'text_padding' => 'text_padding',
+ 'text_angle' => 'text_angle',
+ 'text_align' => 'text_align',
+ ];
+
+ // handle colours first
+ if(isset($g['colour'])) {
+ $lopts['stroke'] = new Colour($this->graph, $g['colour']);
+ $topts['fill'] = new Colour($this->graph, $g['colour']);
+ }
+ if(isset($g['text_colour'])) {
+ // text colour overrides line colour
+ $topts['fill'] = new Colour($this->graph, $g['text_colour']);
+ }
+
+ // font size and line spacing
+ if(isset($g['font_size']))
+ $topts['font-size'] = Number::units($g['font_size']);
+ if(isset($g['line_spacing']))
+ $topts['line_spacing'] = Number::units($g['line_spacing']);
+
+ // copy other options to line or text array
+ foreach($line_opts as $okey => $opt)
+ if(isset($g[$okey]))
+ $lopts[$opt] = $g[$okey];
+ foreach($text_opts as $okey => $opt)
+ if(isset($g[$okey]))
+ $topts[$opt] = $g[$okey];
+
+ if(count($lopts))
+ $guideline['line'] = $lopts;
+ if(count($topts))
+ $guideline['text'] = $topts;
+
+ // update maxima and minima
+ if(!isset($g['no_min_max']) || $g['no_min_max'] === false) {
+ if($this->max_guide[$axis] === null || $mmvalue > $this->max_guide[$axis])
+ $this->max_guide[$axis] = $mmvalue;
+ if($this->min_guide[$axis] === null || $mmvalue < $this->min_guide[$axis])
+ $this->min_guide[$axis] = $mmvalue;
+ }
+
+ // can flip the axes now the min/max are stored
+ if($this->flip_axes)
+ $guideline['axis'] = ($guideline['axis'] == 'x' ? 'y' : 'x');
+
+ $this->guidelines[] = $guideline;
+ }
+
+ /**
+ * Returns the minimum and maximum axis guidelines
+ * array($min_x, $min_y, $max_x, $max_y)
+ */
+ public function getMinMax()
+ {
+ $min_max = [
+ $this->min_guide['x'], $this->min_guide['y'],
+ $this->max_guide['x'], $this->max_guide['y']
+ ];
+ return $min_max;
+ }
+
+ /**
+ * Returns the guidelines above content
+ */
+ public function getAbove()
+ {
+ return $this->get(Guidelines::ABOVE);
+ }
+
+ /**
+ * Returns the guidelines below content
+ */
+ public function getBelow()
+ {
+ return $this->get(Guidelines::BELOW);
+ }
+
+ /**
+ * Returns the elements to draw the guidelines
+ */
+ protected function get($depth)
+ {
+ if(empty($this->guidelines))
+ return '';
+
+ // build all the lines at this depth (above/below) that use
+ // global options as one path
+ $d = $lines = $text = '';
+ $path = [
+ 'stroke' => $this->colour,
+ 'stroke-width' => $this->stroke_width,
+ 'stroke-dasharray' => $this->dash,
+ 'fill' => 'none'
+ ];
+ if($this->opacity != 1)
+ $path['opacity'] = $this->opacity;
+ $textopts = [
+ 'font-family' => $this->font,
+ 'font-size' => $this->font_size,
+ 'font-weight' => $this->font_weight,
+ 'fill' => $this->text_colour,
+ ];
+
+ foreach($this->guidelines as $line) {
+ if($line['depth'] == $depth) {
+ // opacity cannot go in the group because child opacity is multiplied
+ // by group opacity
+ if($this->text_opacity != 1 && !isset($line['text']['opacity']))
+ $line['text']['opacity'] = $this->text_opacity;
+ $this->buildGuideline($line, $lines, $text, $path, $d);
+ }
+ }
+ if(!empty($d)) {
+ $path['d'] = $d;
+ $lines .= $this->graph->element('path', $path);
+ }
+
+ if(!empty($text))
+ $text = $this->graph->element('g', $textopts, null, $text);
+ return $lines . $text;
+ }
+
+ /**
+ * Adds a single guideline and its title to content
+ */
+ protected function buildGuideline(&$line, &$lines, &$text, &$path, &$d)
+ {
+ $length = $this->length;
+ $length_units = $this->length_units;
+ if(isset($line['line'])) {
+ $this->updateAndUnset($length, $line['line'], 'length');
+ $this->updateAndUnset($length_units, $line['line'], 'length_units');
+ }
+
+ $reverse_length = false;
+ $w = $h = 0;
+ if($length != 0) {
+ if($length < 0) {
+ $reverse_length = true;
+ $length = -$length;
+ }
+
+ if($line['axis'] == 'x')
+ $h = $length;
+ else
+ $w = $length;
+
+ } elseif($length_units != 0) {
+ if($length_units < 0) {
+ $reverse_length = true;
+ $length_units = -$length_units;
+ }
+
+ $lnum = new Number($length_units);
+ if($line['axis'] == 'x')
+ $h = 'u' . $lnum;
+ else
+ $w = 'u' . $lnum;
+ }
+
+ // if the graph class has a custom path method, use it
+ // - its signature is the same as GuidelinePath but without $depth
+ $custom_method = ($line['depth'] == Guidelines::ABOVE ?
+ 'guidelinePathAbove' : 'guidelinePathBelow');
+
+ if(method_exists($this->graph, $custom_method)) {
+ $path_data = $this->graph->{$custom_method}($line['axis'], $line['value'],
+ $x, $y, $w, $h, $reverse_length);
+ } else {
+ $path_data = $this->guidelinePath($line['axis'], $line['value'],
+ $line['depth'], $x, $y, $w, $h, $reverse_length);
+ }
+ if($path_data == '')
+ return;
+
+ if(!isset($line['line'])) {
+ // no special options, add to main path
+ $d .= $path_data;
+ } else {
+ $line_path = array_merge($path, $line['line'], ['d' => $path_data]);
+ $lines .= $this->graph->element('path', $line_path);
+ }
+ if(!empty($line['title'])) {
+ $text_pos = $this->text_position;
+ $text_pad = $this->text_padding;
+ $text_angle = $this->text_angle;
+ $text_align = $this->text_align;
+ $font = $this->font;
+ $font_size = $this->font_size;
+ $font_adjust = $this->font_adjust;
+ $line_spacing = $this->line_spacing;
+ if(isset($line['text'])) {
+ $this->updateAndUnset($text_pos, $line['text'], 'text_position');
+ $this->updateAndUnset($text_pad, $line['text'], 'text_padding');
+ $this->updateAndUnset($text_angle, $line['text'], 'text_angle');
+ $this->updateAndUnset($text_align, $line['text'], 'text_align');
+ $this->updateAndUnset($font_adjust, $line['text'], 'font_adjust');
+ $this->updateAndUnset($line_spacing, $line['text'], 'line_spacing');
+ if(isset($line['text']['font-family']))
+ $font = $line['text']['font-family'];
+ if(isset($line['text']['font-size']))
+ $font_size = $line['text']['font-size'];
+ }
+ if($line_spacing === null || $line_spacing < 1)
+ $line_spacing = $font_size;
+
+ $svg_text = new Text($this->graph, $font, $font_adjust);
+ list($text_w, $text_h) = $svg_text->measure($line['title'], $font_size,
+ $text_angle, $line_spacing);
+
+ list($x, $y, $text_pos_align) = Graph::relativePosition(
+ $text_pos, $y, $x, $y + $h, $x + $w,
+ $text_w, $text_h, $text_pad, true);
+
+ $t = ['x' => $x, 'y' => $y + $svg_text->baseline($font_size)];
+ if(empty($text_align) && $text_pos_align != 'start') {
+ $t['text-anchor'] = $text_pos_align;
+ } else {
+ $align_map = ['right' => 'end', 'centre' => 'middle'];
+ if(isset($align_map[$text_align]))
+ $t['text-anchor'] = $align_map[$text_align];
+ }
+
+ if($text_angle != 0) {
+ $rx = $x + $text_h/2;
+ $ry = $y + $text_h/2;
+ $xform = new Transform;
+ $xform->rotate($text_angle, $rx, $ry);
+ $t['transform'] = $xform;
+ }
+
+ if(isset($line['text']))
+ $t = array_merge($t, $line['text']);
+ $text .= $svg_text->text($line['title'], $line_spacing, $t);
+ }
+ }
+
+ /**
+ * Creates the path data for a guideline and sets the dimensions
+ */
+ protected function guidelinePath($axis, $value, $depth, &$x, &$y, &$w, &$h,
+ $reverse_length)
+ {
+ // use the Coords class to find measurements
+ $strvalue = (string)(is_numeric($value) ? new Number($value) : $value);
+ if($axis == 'x') {
+ $y = $this->coords->transform('gt', 'y');
+ $x = $this->coords->transform('g' . $strvalue, 'x', null);
+ if($x === null)
+ return new PathData;
+
+ if(is_string($h)) {
+ $h = $this->coords->transform($h, 'y');
+ } elseif($h <= 0) {
+ $h = $this->coords->transform('gh', 'y');
+ }
+ if(!$reverse_length)
+ $y = $this->coords->transform('gb', 'y') - $h;
+ return new PathData('M', $x, $y, 'v', $h);
+ } else {
+ $x = $this->coords->transform('gl', 'x');
+ $y = $this->coords->transform('g' . $strvalue, 'y', null);
+ if($y === null)
+ return new PathData;
+
+ if(is_string($w)) {
+ $w = $this->coords->transform($w, 'x');
+ } elseif($w <= 0) {
+ $w = $this->coords->transform('gw', 'x');
+ }
+ if($reverse_length)
+ $x = $this->coords->transform('gr', 'x') - $w;
+ $h = 0;
+ return new PathData('M', $x, $y, 'h', $w);
+ }
+ }
+
+ /**
+ * Updates $var with $array[$key] and removes it from array
+ */
+ protected function updateAndUnset(&$var, &$array, $key)
+ {
+ if(isset($array[$key])) {
+ $var = $array[$key];
+ unset($array[$key]);
+ }
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/Histogram.php b/classes/vendor/81x/goat1000/svggraph/Histogram.php
new file mode 100644
index 0000000..50a32a6
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/Histogram.php
@@ -0,0 +1,250 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class Histogram extends BarGraph {
+
+ private $scaling = 1;
+ private $increment = 1;
+
+ public function __construct($w, $h, array $settings, array $fixed_settings = [])
+ {
+ $fs = [
+ 'repeated_keys' => 'accept',
+ 'label_centre' => false,
+
+ // disable datetime conversion
+ 'datetime_keys' => false,
+ ];
+ $fs = array_merge($fs, $fixed_settings);
+ parent::__construct($w, $h, $settings, $fs);
+ }
+
+ /**
+ * Process the values
+ */
+ public function values($values)
+ {
+ if(!empty($values)) {
+ parent::values($values);
+ if($this->values->error)
+ return;
+ $values = [];
+
+ // find min, max, strip out nulls
+ $min = $max = null;
+ $dataset = $this->getOption(['dataset', 0], 0);
+ foreach($this->values[$dataset] as $item) {
+ if($item->value !== null) {
+ if(!is_numeric($item->value)) {
+ $this->values->error = 'Non-numeric value';
+ return;
+ }
+
+ if($min === null || $item->value < $min)
+ $min = $item->value;
+ if($max === null || $item->value > $max)
+ $max = $item->value;
+ $values[] = $item->value;
+ }
+ }
+
+ // calculate or clean up increment
+ $inc = $this->getOption('increment');
+ if($inc <= 0) {
+ $diff = $max - $min;
+ if($diff <= 0) {
+ $inc = 1;
+ } else {
+ $scale = floor(log10($diff));
+ $inc = pow(10, $scale);
+ $d1 = $diff / $inc;
+ if(($inc != 1 || !is_integer($diff)) && $d1 < 4) {
+ if($d1 < 3)
+ $inc *= 0.2;
+ else
+ $inc *= 0.5;
+ }
+ }
+ }
+
+ // need to scale if $inc not an integer
+ $s = 1;
+ while($inc < 1 || ($inc != floor($inc))) {
+ $s *= 10;
+ $inc *= 10;
+ }
+ if($s > 1) {
+ $this->scaling = $s;
+ $max *= $s;
+ $min *= $s;
+ $diff = $max - $min;
+ }
+ $this->increment = $inc;
+
+ // prefill the map with nulls
+ $map = [];
+ $start = $this->interval($min);
+ $end = $this->interval($max, true) + $this->increment / 2;
+
+ Number::setup($this->getOption('precision'), $this->getOption('decimal'),
+ $this->getOption('thousands'));
+ for($i = $start; $i < $end; $i += $this->increment) {
+ $key = (int)$i;
+ $map[$key] = 0;
+ }
+
+ foreach($values as $val) {
+ $val *= $this->scaling;
+ $k = (int)$this->interval($val);
+ if(!array_key_exists($k, $map))
+ $map[$k] = 1;
+ else
+ $map[$k]++;
+ }
+
+ if($this->getOption('percentage')) {
+ $total = count($values);
+ $pmap = [];
+ foreach($map as $k => $v)
+ $pmap[$k] = 100 * $v / $total;
+ $values = $pmap;
+ } else {
+ $values = $map;
+ }
+
+ // turn into structured data
+ $new_values = [];
+ foreach($values as $k => $v)
+ $new_values[] = [$k, $v];
+ $values = $new_values;
+
+ // make structure use the same dataset number
+ $structure = [ 'key' => 0, 'value' => 1 ];
+ if($dataset !== 0) {
+ $structure['value'] = array_fill(0, $dataset, 2);
+ $structure['value'][$dataset] = 1;
+ }
+
+ $this->setOption('structure', $structure);
+ $this->setOption('structured_data', true);
+
+ // set up options to make bar graph class draw the histogram properly
+ $this->setOption('minimum_units_y', 1);
+ $this->setOption('subdivision_h', $this->increment); // no subdiv below bar size
+ $this->setOption('grid_division_h', $this->increment); //max($increment, $this->grid_division_h));
+
+ $amh = $this->getOption('axis_min_h');
+ if(empty($amh))
+ $this->setOption('axis_min_h', $start);
+ if($this->scaling !== 1) {
+ $this->setOption('axis_text_callback_x', function($v) {
+ $s = $this->scaling;
+ $p = log10($this->scaling) + 1;
+ $n = new Number($v / $s);
+ return $n->format(null, $p);
+ });
+ }
+ }
+ parent::values($values);
+ }
+
+ /**
+ * Returns the start (or next) interval for a value
+ */
+ public function interval($value, $next = false)
+ {
+ $n = floor($value / $this->increment);
+ if($next)
+ ++$n;
+ return $n * $this->increment;
+ }
+
+ /**
+ * Sets up the colour class with corrected number of colours
+ */
+ protected function colourSetup($count, $datasets = null, $reverse = false)
+ {
+ // $count is off by 1 because the divisions are numbered
+ return parent::colourSetup($count - 1, $datasets, $reverse);
+ }
+
+ /**
+ * Override because of the shifted numbering
+ */
+ protected function gridPosition($item, $ikey)
+ {
+ $position = null;
+ $zero = -0.01; // catch values close to 0
+ $axis = $this->x_axes[$this->main_x_axis];
+ $offset = $axis->position($item->key);
+ $g_limit = $this->g_width - ($axis->unit() / 2);
+ if($offset >= $zero && floor($offset) <= $g_limit)
+ $position = $this->pad_left + $offset;
+
+ return $position;
+ }
+
+ /**
+ * Returns the width of a bar
+ */
+ protected function barWidth()
+ {
+ $bar_width = $this->getOption('bar_width');
+ if(is_numeric($bar_width) && $bar_width >= 1)
+ return $bar_width;
+ $unit_w = $this->increment *
+ $this->x_axes[$this->main_x_axis]->unit();
+ return max(1, $unit_w - $this->getOption('bar_space'));
+ }
+
+ /**
+ * Returns the space before a bar
+ */
+ protected function barSpace($bar_width)
+ {
+ $uwidth = $this->increment *
+ $this->x_axes[$this->main_x_axis]->unit();
+ return max(0, ($uwidth - $bar_width) / 2);
+ }
+
+ /**
+ * Override to prevent drawing an entry past the last bar
+ */
+ protected function setLegendEntry($dataset, $index, $item, $style_info)
+ {
+ // the last entry is a blank to wangle the numbering
+ if($item->key >= $this->getMaxKey())
+ return;
+ parent::setLegendEntry($dataset, $index, $item, $style_info);
+ }
+
+ /**
+ * Override to pass in the modified Average class to use
+ */
+ protected function calcAverages($cls = 'Goat1000\SVGGraph\HistogramAverage')
+ {
+ return parent::calcAverages('Goat1000\SVGGraph\HistogramAverage');
+ }
+
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/HistogramAverage.php b/classes/vendor/81x/goat1000/svggraph/HistogramAverage.php
new file mode 100644
index 0000000..f178223
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/HistogramAverage.php
@@ -0,0 +1,48 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+/**
+ * Class for making averages work on Histograms
+ */
+class HistogramAverage extends Average {
+
+ /**
+ * Calculates the mean average for a dataset
+ */
+ protected function calculate(&$values, $dataset)
+ {
+ $sum = 0;
+ $count = 0;
+ foreach($values[$dataset] as $p) {
+ if($p->value === null)
+ continue;
+ $sum += $p->value;
+ ++$count;
+ }
+
+ // histogram data ends with a 0 to pad the axis out
+ --$count;
+
+ return $count > 0 ? $sum / $count : null;
+ }
+}
diff --git a/classes/vendor/81x/goat1000/svggraph/HorizontalBar3DGraph.php b/classes/vendor/81x/goat1000/svggraph/HorizontalBar3DGraph.php
new file mode 100644
index 0000000..31be66a
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/HorizontalBar3DGraph.php
@@ -0,0 +1,92 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class HorizontalBar3DGraph extends HorizontalThreeDGraph {
+
+ use HorizontalBarGraphTrait {
+ barGroup as traitBarGroup;
+ setBarWidth as traitSetBarWidth;
+ }
+
+ protected $tx;
+ protected $ty;
+ protected $bar_class = 'Goat1000\\SVGGraph\\Bar3D';
+ protected $bar_drawer;
+
+ public function __construct($w, $h, array $settings, array $fixed_settings = [])
+ {
+ $fs = ['label_centre' => !isset($settings['datetime_keys'])];
+ $fs = array_merge($fs, $fixed_settings);
+ parent::__construct($w, $h, $settings, $fs);
+
+ $this->bar_drawer = new $this->bar_class($this);
+ }
+
+ /**
+ * Returns the SVG code for a 3D bar
+ */
+ protected function bar3D($item, &$bar, $top, $index, $dataset = null,
+ $start = null, $axis = null)
+ {
+ $pos = $this->barX($item, $index, $tmp_bar, $axis, $dataset);
+ if($pos === null || $pos > $this->height - $this->pad_bottom)
+ return '';
+
+ return $this->bar_drawer->draw($bar['x'], $bar['y'],
+ $bar['width'], $bar['height'], true, $top,
+ $this->getColour($item, $index, $dataset, false, false));
+ }
+
+ /**
+ * Set the bar width and space, create the top
+ */
+ protected function setBarWidth($width, $space)
+ {
+ $this->traitSetBarWidth($width, $space);
+ $this->bar_drawer->setDepth($width);
+ list($this->tx, $this->ty) = $this->project(0, 0, $space);
+ }
+
+ /**
+ * Add the translation to the bar group
+ */
+ protected function barGroup()
+ {
+ $group = $this->traitBarGroup();
+ if($this->tx || $this->ty) {
+ $xform = new Transform;
+ $xform->translate($this->tx, $this->ty);
+ $group['transform'] = $xform;
+ }
+ return $group;
+ }
+
+ /**
+ * Returns the SVG code for a bar
+ */
+ protected function drawBar(DataItem $item, $index, $start = 0, $axis = null,
+ $dataset = 0, $options = [])
+ {
+ return $this->drawBar3D($item, $index, $start, $axis, $dataset, $options);
+ }
+}
diff --git a/classes/vendor/81x/goat1000/svggraph/HorizontalBarGraph.php b/classes/vendor/81x/goat1000/svggraph/HorizontalBarGraph.php
new file mode 100644
index 0000000..48973a8
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/HorizontalBarGraph.php
@@ -0,0 +1,27 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class HorizontalBarGraph extends HorizontalGridGraph {
+
+ use HorizontalBarGraphTrait;
+}
diff --git a/classes/vendor/81x/goat1000/svggraph/HorizontalBarGraphTrait.php b/classes/vendor/81x/goat1000/svggraph/HorizontalBarGraphTrait.php
new file mode 100644
index 0000000..5f2af54
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/HorizontalBarGraphTrait.php
@@ -0,0 +1,149 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+trait HorizontalBarGraphTrait {
+
+ use BarGraphTrait;
+
+ public function __construct($w, $h, array $settings, array $fixed_settings = [])
+ {
+ // backwards compatibility
+ if(isset($settings['show_bar_labels']) && !isset($settings['show_data_labels']))
+ $settings['show_data_labels'] = $settings['show_bar_labels'];
+
+ $fs = ['label_centre' => !isset($settings['datetime_keys'])];
+ $fs = array_merge($fs, $fixed_settings);
+ parent::__construct($w, $h, $settings, $fs);
+ }
+
+ /**
+ * Returns the height of a bar rectangle
+ */
+ protected function barWidth()
+ {
+ if(is_numeric($this->getOption('bar_width')) && $this->getOption('bar_width') >= 1)
+ return $this->getOption('bar_width');
+ $unit_h = $this->y_axes[$this->main_y_axis]->unit();
+ $bh = $unit_h - $this->getOption('bar_space');
+ return max(1, $bh, $this->getOption('bar_width_min'));
+ }
+
+ /**
+ * Fills in the x-position and width of a bar
+ * @param number $value bar value
+ * @param array &$bar bar element array [out]
+ * @param number $start bar start value
+ * @return number unclamped bar position
+ */
+ protected function barY($value, &$bar, $start = null)
+ {
+ if($start)
+ $value += $start;
+
+ $startpos = $start === null ? $this->originX() : $this->gridX($start);
+ if($startpos === null)
+ $startpos = $this->originX();
+ $pos = $this->gridX($value);
+ if($pos === null) {
+ $bar['width'] = 0;
+ } else {
+ $l1 = $this->clampHorizontal($startpos);
+ $l2 = $this->clampHorizontal($pos);
+ $bar['x'] = min($l1, $l2);
+ $bar['width'] = abs($l1-$l2);
+ }
+ return $pos;
+ }
+
+ /**
+ * Fills in the y and height of bar
+ */
+ protected function barX($item, $index, &$bar, $axis, $dataset)
+ {
+ $bar_y = $this->gridPosition($item, $index);
+ if($bar_y === null)
+ return null;
+
+ $axis = $this->y_axes[$this->main_y_axis];
+ if($axis->reversed())
+ $bar['y'] = $bar_y - $this->calculated_bar_space - $this->calculated_bar_width;
+ else
+ $bar['y'] = $bar_y + $this->calculated_bar_space;
+ $bar['height'] = $this->calculated_bar_width;
+ return $bar_y;
+ }
+
+ /**
+ * Returns the space before a bar
+ */
+ protected function barSpace($bar_width)
+ {
+ return max(0, ($this->y_axes[$this->main_y_axis]->unit() - $bar_width) / 2);
+ }
+
+ /**
+ * Override to check minimum space requirement
+ */
+ protected function addDataLabel($dataset, $index, &$element, &$item,
+ $x, $y, $w, $h, $content = null, $duplicate = true)
+ {
+ if($w < $this->getOption(['data_label_min_space', $dataset]))
+ return false;
+ return parent::addDataLabel($dataset, $index, $element, $item, $x, $y,
+ $w, $h, $content, $duplicate);
+ }
+
+ /**
+ * Returns the position for a data label
+ */
+ public function dataLabelPosition($dataset, $index, &$item, $x, $y, $w, $h,
+ $label_w, $label_h)
+ {
+ list($pos, $target) = parent::dataLabelPosition($dataset, $index, $item,
+ $x, $y, $w, $h, $label_w, $label_h);
+ $bpos = $this->getOption('bar_label_position');
+ if(!empty($bpos))
+ $pos = $bpos;
+
+ if($label_w > $w && Graph::isPositionInside($pos))
+ $pos = str_replace(['left','centre','right'], 'outside right inside', $pos);
+
+ // flip sides for negative values
+ if($item !== null && $item->value < 0) {
+ if(strpos($pos, 'right') !== false)
+ $pos = str_replace('right', 'left', $pos);
+ elseif(strpos($pos, 'left') !== false)
+ $pos = str_replace('left', 'right', $pos);
+ }
+ return [$pos, $target];
+ }
+
+ /**
+ * Returns the ordering for legend entries
+ */
+ public function getLegendOrder()
+ {
+ return 'reverse';
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/HorizontalFloatingBarGraph.php b/classes/vendor/81x/goat1000/svggraph/HorizontalFloatingBarGraph.php
new file mode 100644
index 0000000..04599d7
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/HorizontalFloatingBarGraph.php
@@ -0,0 +1,35 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class HorizontalFloatingBarGraph extends HorizontalBarGraph {
+
+ use FloatingBarTrait;
+
+ public function __construct($w, $h, array $settings, array $fixed_settings = [])
+ {
+ $fs = ['require_structured' => ['end']];
+ $fs = array_merge($fs, $fixed_settings);
+ parent::__construct($w, $h, $settings, $fs);
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/HorizontalGridGraph.php b/classes/vendor/81x/goat1000/svggraph/HorizontalGridGraph.php
new file mode 100644
index 0000000..fc1046f
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/HorizontalGridGraph.php
@@ -0,0 +1,272 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+abstract class HorizontalGridGraph extends GridGraph {
+
+ public function __construct($w, $h, array $settings, array $fixed_settings = [])
+ {
+ // convert _x and _y labels to _v and _h
+ if(!empty($settings['label_x']) && empty($settings['label_v']))
+ $settings['label_v'] = $settings['label_x'];
+ if(!empty($settings['label_y']) && empty($settings['label_h']))
+ $settings['label_h'] = $settings['label_y'];
+
+ // unset these so GridGraph doesn't use them
+ unset($settings['label_x'], $settings['label_y']);
+
+ parent::__construct($w, $h, $settings, $fixed_settings);
+ }
+
+ /**
+ * Swap the axis returned
+ */
+ protected function getDisplayAxis($axis, $axis_no, $orientation, $type)
+ {
+ return parent::getDisplayAxis($axis, $axis_no, $orientation,
+ $type === 'x' ? 'y' : 'x');
+ }
+
+ /**
+ * Returns the factory for an X-axis
+ */
+ protected function getXAxisFactory()
+ {
+ return new AxisFactory(false, $this->settings, false, false, false);
+ }
+
+ /**
+ * Returns the factory for a Y-axis
+ */
+ protected function getYAxisFactory()
+ {
+ $y_bar = $this->getOption('label_centre');
+ return new AxisFactory($this->getOption('datetime_keys'), $this->settings,
+ true, $y_bar, true);
+ }
+
+ /**
+ * Creates and returns an X-axis
+ */
+ protected function createXAxis($factory, $length, $ends, $i, $min_space, $grid_division)
+ {
+ $max_h = $ends['v_max'][$i];
+ $min_h = $ends['v_min'][$i];
+ if(!is_numeric($max_h) || !is_numeric($min_h))
+ throw new \Exception('Non-numeric min/max');
+
+ $min_unit = $this->getOption(['minimum_units_y', $i]);
+ $units_after = (string)$this->getOption(['units_y', $i]);
+ $units_before = (string)$this->getOption(['units_before_y', $i]);
+ $decimal_digits = $this->getOption(['decimal_digits_y', $i],
+ 'decimal_digits');
+ $text_callback = $this->getOption(['axis_text_callback_y', $i],
+ 'axis_text_callback');
+ $log = $this->getOption(['log_axis_y', $i]);
+ $log_base = $this->getOption(['log_axis_y_base', $i]);
+ $ticks = $this->getOption(['axis_ticks_y', $i]);
+ $values = $levels = null;
+
+ if($min_h == $max_h) {
+ if($min_unit > 0) {
+ $inc = $min_unit;
+ } else {
+ $fallback = $this->getOption('axis_fallback_max');
+ $inc = $fallback > 0 ? $fallback : 1;
+ }
+ $max_h += $inc;
+ }
+
+ $x_axis = $factory->get($length, $min_h, $max_h, $min_unit,
+ $min_space, $grid_division, $units_before, $units_after,
+ $decimal_digits, $text_callback, $values, $log, $log_base, $levels, $ticks);
+ $x_axis->setTightness($this->getOption(['axis_tightness_y', $i]));
+ return $x_axis;
+ }
+
+ /**
+ * Creates and returns a Y-axis
+ */
+ protected function createYAxis($factory, $length, $ends, $i, $min_space, $grid_division)
+ {
+ $max_v = $ends['k_max'][$i];
+ $min_v = $ends['k_min'][$i];
+ if(!is_numeric($max_v) || !is_numeric($min_v))
+ throw new \Exception('Non-numeric min/max');
+
+ $min_unit = 1;
+ $text_callback = $this->getOption(['axis_text_callback_x', $i],
+ 'axis_text_callback');
+ $decimal_digits = $this->getOption(['decimal_digits_x', $i],
+ 'decimal_digits');
+ $units_after = (string)$this->getOption(['units_x', $i]);
+ $units_before = (string)$this->getOption(['units_before_x', $i]);
+ $values = $this->multi_graph ? $this->multi_graph : $this->values;
+ $levels = $this->getOption(['axis_levels_h', $i]);
+ $log = $this->getOption(['log_axis_x', $i]);
+ $log_base = $this->getOption(['log_axis_x_base', $i]);
+ $ticks = $this->getOption('axis_ticks_x');
+
+ return $factory->get($length, $min_v, $max_v, $min_unit,
+ $min_space, $grid_division, $units_before, $units_after,
+ $decimal_digits, $text_callback, $values, $log, $log_base, $levels, $ticks);
+ }
+
+ /**
+ * Calculates the position of grid lines
+ */
+ protected function calcGrid()
+ {
+ parent::calcGrid();
+
+ $this->grid_limit = 0.01 + $this->g_height;
+ if($this->getOption('label_centre')) {
+ $y_axis = $this->y_axes[$this->main_y_axis];
+ $this->grid_limit -= $y_axis->unit() / 2;
+ }
+ }
+
+ /**
+ * Returns the subdivisions for a Y-axis
+ */
+ protected function getSubDivsY($axis)
+ {
+ $a = $this->y_axes[$axis];
+ $offset = $a->reversed() ? $this->height - $this->pad_bottom : $this->pad_top;
+ return $a->getGridSubdivisions($this->getOption('minimum_subdivision'), 1,
+ $offset, $this->getOption(['subdivision_v', $axis]));
+ }
+
+ /**
+ * Returns the subdivisions for an X-axis
+ */
+ protected function getSubDivsX($axis)
+ {
+ $a = $this->x_axes[$axis];
+ $offset = $a->reversed() ? $this->width - $this->pad_right : $this->pad_left;
+ return $a->getGridSubdivisions($this->getOption('minimum_subdivision'),
+ $this->getOption(['minimum_units_y', $axis]), $offset,
+ $this->getOption(['subdivision_h', $axis]));
+ }
+
+ /**
+ * Returns fixed min and max option for an axis
+ */
+ protected function getFixedAxisOptions($axis, $index)
+ {
+ $a = $axis == 'y' ? 'h' : 'v';
+ $min = $this->getOption(['axis_min_' . $a, $index]);
+ $max = $this->getOption(['axis_max_' . $a, $index]);
+ return [$min, $max];
+ }
+
+ /**
+ * Returns the crosshairs code
+ */
+ protected function getCrossHairs()
+ {
+ if(!$this->getOption('crosshairs'))
+ return '';
+
+ $ch = new CrossHairs($this, $this->pad_left, $this->pad_top,
+ $this->g_width, $this->g_height, $this->x_axes[$this->main_x_axis],
+ $this->y_axes[$this->main_y_axis], $this->values->associativeKeys(),
+ true, $this->encoding);
+
+ if($ch->enabled())
+ return $ch->getCrossHairs();
+ return '';
+ }
+
+ /**
+ * Returns the grid position for a bar or point, or NULL if not on grid
+ * $item = data item
+ * $index = integer position in array
+ */
+ protected function gridPosition($item, $index)
+ {
+ $axis = $this->y_axes[$this->main_y_axis];
+ $offset = $axis->position($index, $item);
+ $zero = -0.01; // catch values close to 0
+
+ if($offset >= $zero && floor($offset) <= $this->grid_limit)
+ return $axis->reversed() ? $this->height - $this->pad_bottom - $offset :
+ $this->pad_top + $offset;
+
+ return null;
+ }
+
+ /**
+ * Returns the SVG fragment for grid background stripes
+ */
+ protected function getGridStripes()
+ {
+ if(!$this->getOption('grid_back_stripe'))
+ return '';
+
+ // use array of colours if available, otherwise stripe a single colour
+ $colours = $this->getOption('grid_back_stripe_colour');
+ if(!is_array($colours))
+ $colours = [null, $colours];
+ $opacity = $this->getOption('grid_back_stripe_opacity');
+
+ $bars = '';
+ $c = 0;
+ $num_colours = count($colours);
+
+ $rect = [
+ 'y' => new Number($this->pad_top),
+ 'height' => new Number($this->g_height),
+ ];
+ if($opacity != 1)
+ $rect['fill-opacity'] = $opacity;
+ $points = $this->getGridPointsX($this->main_x_axis);
+ $first = array_shift($points);
+ $last_pos = $first->position;
+ foreach($points as $grid_point) {
+ $cc = $colours[$c % $num_colours];
+ if($cc !== null) {
+ $rect['x'] = $last_pos;
+ $rect['width'] = $grid_point->position - $last_pos;
+ $rect['fill'] = new Colour($this, $cc);
+ $bars .= $this->element('rect', $rect);
+ }
+ $last_pos = $grid_point->position;
+ ++$c;
+ }
+ return $this->element('g', [], null, $bars);
+ }
+
+ /**
+ * Converts guideline options to more useful member variables
+ */
+ protected function calcGuidelines()
+ {
+ $this->calcAverages();
+ $guidelines = $this->getOption('guideline');
+ if(empty($guidelines) && $guidelines !== 0)
+ return;
+
+ $this->guidelines = new Guidelines($this, true,
+ $this->values->associativeKeys(), $this->getOption('datetime_keys'));
+ }
+}
diff --git a/classes/vendor/81x/goat1000/svggraph/HorizontalGroupedBar3DGraph.php b/classes/vendor/81x/goat1000/svggraph/HorizontalGroupedBar3DGraph.php
new file mode 100644
index 0000000..b23d8e2
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/HorizontalGroupedBar3DGraph.php
@@ -0,0 +1,77 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class HorizontalGroupedBar3DGraph extends HorizontalBar3DGraph {
+
+ use Grouped3DGraphTrait;
+
+ public function __construct($w, $h, $settings, $fixed_settings = [])
+ {
+ $fixed = [ 'single_axis' => true ];
+ $fixed_settings = array_merge($fixed, $fixed_settings);
+ parent::__construct($w, $h, $settings, $fixed_settings);
+ }
+
+ /**
+ * Sets up bar details
+ */
+ protected function barSetup()
+ {
+ parent::barSetup();
+ $datasets = $this->multi_graph->getEnabledDatasets();
+ $dataset_count = count($datasets);
+
+ list($chunk_width, $bspace, $chunk_unit_width) =
+ $this->barPosition($this->getOption('bar_width'), $this->getOption('bar_width_min'),
+ $this->y_axes[$this->main_y_axis]->unit(), $dataset_count,
+ $this->getOption('bar_space'), $this->getOption('group_space'));
+ $this->group_bar_spacing = $chunk_unit_width;
+ $this->setBarWidth($chunk_width, $bspace);
+
+ $offset = 0;
+ foreach($datasets as $d) {
+ $this->dataset_offsets[$d] = $offset;
+ ++$offset;
+ }
+ }
+
+ /**
+ * Returns an array with x, y, width and height set
+ */
+ protected function barDimensions($item, $index, $start, $axis, $dataset)
+ {
+ $bar_y = $this->gridPosition($item, $index);
+ if($bar_y === null)
+ return [];
+
+ $d_offset = $this->dataset_offsets[$dataset];
+ $bar = [
+ 'y' => $bar_y - $this->calculated_bar_space -
+ ($d_offset * $this->group_bar_spacing) - $this->calculated_bar_width,
+ 'height' => $this->calculated_bar_width,
+ ];
+
+ $this->barY($item->value, $bar, $start, $axis);
+ return $bar;
+ }
+}
diff --git a/classes/vendor/81x/goat1000/svggraph/HorizontalGroupedBarGraph.php b/classes/vendor/81x/goat1000/svggraph/HorizontalGroupedBarGraph.php
new file mode 100644
index 0000000..0b4eae6
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/HorizontalGroupedBarGraph.php
@@ -0,0 +1,79 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class HorizontalGroupedBarGraph extends HorizontalBarGraph {
+
+ use GroupedBarTrait;
+
+ public function __construct($w, $h, $settings, $fixed_settings = [])
+ {
+ $fixed = [ 'single_axis' => true ];
+ $fixed_settings = array_merge($fixed, $fixed_settings);
+ parent::__construct($w, $h, $settings, $fixed_settings);
+ }
+
+ /**
+ * Sets up bar details
+ */
+ protected function barSetup()
+ {
+ parent::barSetup();
+ $datasets = $this->multi_graph->getEnabledDatasets();
+ $dataset_count = count($datasets);
+
+ list($chunk_width, $bspace, $chunk_unit_width) =
+ $this->barPosition($this->getOption('bar_width'),
+ $this->getOption('bar_width_min'),
+ $this->y_axes[$this->main_y_axis]->unit(), $dataset_count,
+ $this->getOption('bar_space'), $this->getOption('group_space'));
+ $this->group_bar_spacing = $chunk_unit_width;
+ $this->setBarWidth($chunk_width, $bspace);
+
+ $offset = 0;
+ foreach($datasets as $d) {
+ $this->dataset_offsets[$d] = $offset;
+ ++$offset;
+ }
+ }
+
+ /**
+ * Returns an array with x, y, width and height set
+ */
+ protected function barDimensions($item, $index, $start, $axis, $dataset)
+ {
+ $bar_y = $this->gridPosition($item, $index);
+ if($bar_y === null)
+ return [];
+
+ $d_offset = $this->dataset_offsets[$dataset];
+ $bar = [
+ 'y' => $bar_y - $this->calculated_bar_space -
+ ($d_offset * $this->group_bar_spacing) - $this->calculated_bar_width,
+ 'height' => $this->calculated_bar_width,
+ ];
+
+ $this->barY($item->value, $bar, $start, $axis);
+ return $bar;
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/HorizontalStackedBar3DGraph.php b/classes/vendor/81x/goat1000/svggraph/HorizontalStackedBar3DGraph.php
new file mode 100644
index 0000000..5c9a441
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/HorizontalStackedBar3DGraph.php
@@ -0,0 +1,87 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class HorizontalStackedBar3DGraph extends HorizontalBar3DGraph {
+
+ use StackedBarTrait;
+
+ public function __construct($w, $h, $settings, $fixed_settings = [])
+ {
+ $fixed = [ 'single_axis' => true ];
+ $fixed_settings = array_merge($fixed, $fixed_settings);
+ parent::__construct($w, $h, $settings, $fixed_settings);
+ }
+
+ /**
+ * Trait version draws totals above or below bars, we want left and right
+ */
+ public function dataLabelPosition($dataset, $index, &$item, $x, $y, $w, $h,
+ $label_w, $label_h)
+ {
+ list($pos, $target) = parent::dataLabelPosition($dataset, $index, $item,
+ $x, $y, $w, $h, $label_w, $label_h);
+ if(!is_numeric($dataset)) {
+ list($d) = explode('-', $dataset);
+ if($d === 'totalpos') {
+ if(isset($this->last_position_pos[$index])) {
+ list($lpos, $l_w) = $this->last_position_pos[$index];
+ list($hpos, $vpos) = Graph::translatePosition($lpos);
+ if($hpos == 'or') {
+ $num_offset = new Number($l_w);
+ return ['middle outside right ' . $num_offset . ' 0', $target];
+ }
+ }
+ return ['outside right', $target];
+ }
+ if($d === 'totalneg') {
+ if(isset($this->last_position_neg[$index])) {
+ list($lpos, $l_w) = $this->last_position_neg[$index];
+ list($hpos, $vpos) = Graph::translatePosition($lpos);
+ if($hpos == 'ol') {
+ $num_offset = new Number(-$l_w);
+ return ['middle outside left ' . $num_offset . ' 0', $target];
+ }
+ }
+ return ['outside left', $target];
+ }
+ }
+ if($label_w > $w && Graph::isPositionInside($pos))
+ $pos = str_replace(['outside left','outside right'], 'centre', $pos);
+
+ if($item->value > 0)
+ $this->last_position_pos[$index] = [$pos, $label_w];
+ else
+ $this->last_position_neg[$index] = [$pos, $label_w];
+ return [$pos, $target];
+ }
+
+ /**
+ * Returns the ordering for legend entries
+ */
+ public function getLegendOrder()
+ {
+ // bars are stacked from left to right
+ return null;
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/HorizontalStackedBarGraph.php b/classes/vendor/81x/goat1000/svggraph/HorizontalStackedBarGraph.php
new file mode 100644
index 0000000..5d6f92b
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/HorizontalStackedBarGraph.php
@@ -0,0 +1,87 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class HorizontalStackedBarGraph extends HorizontalBarGraph {
+
+ use StackedBarTrait;
+
+ public function __construct($w, $h, $settings, $fixed_settings = [])
+ {
+ $fixed = [ 'single_axis' => true ];
+ $fixed_settings = array_merge($fixed, $fixed_settings);
+ parent::__construct($w, $h, $settings, $fixed_settings);
+ }
+
+ /**
+ * Trait version draws totals above or below bars, we want left and right
+ */
+ public function dataLabelPosition($dataset, $index, &$item, $x, $y, $w, $h,
+ $label_w, $label_h)
+ {
+ list($pos, $target) = parent::dataLabelPosition($dataset, $index, $item,
+ $x, $y, $w, $h, $label_w, $label_h);
+ if(!is_numeric($dataset)) {
+ list($d) = explode('-', $dataset);
+ if($d === 'totalpos') {
+ if(isset($this->last_position_pos[$index])) {
+ list($lpos, $l_w) = $this->last_position_pos[$index];
+ list($hpos, $vpos) = Graph::translatePosition($lpos);
+ if($hpos == 'or') {
+ $num_offset = new Number($l_w);
+ return ['middle outside right ' . $num_offset . ' 0', $target];
+ }
+ }
+ return ['outside right', $target];
+ }
+ if($d === 'totalneg') {
+ if(isset($this->last_position_neg[$index])) {
+ list($lpos, $l_w) = $this->last_position_neg[$index];
+ list($hpos, $vpos) = Graph::translatePosition($lpos);
+ if($hpos == 'ol') {
+ $num_offset = new Number(-$l_w);
+ return ['middle outside left ' . $num_offset . ' 0', $target];
+ }
+ }
+ return ['outside left', $target];
+ }
+ }
+ if($label_w > $w && Graph::isPositionInside($pos))
+ $pos = str_replace(['outside left','outside right'], 'centre', $pos);
+
+ if($item->value > 0)
+ $this->last_position_pos[$index] = [$pos, $label_w];
+ else
+ $this->last_position_neg[$index] = [$pos, $label_w];
+ return [$pos, $target];
+ }
+
+ /**
+ * Returns the ordering for legend entries
+ */
+ public function getLegendOrder()
+ {
+ // bars are stacked from left to right
+ return null;
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/HorizontalThreeDGraph.php b/classes/vendor/81x/goat1000/svggraph/HorizontalThreeDGraph.php
new file mode 100644
index 0000000..ab01c63
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/HorizontalThreeDGraph.php
@@ -0,0 +1,113 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+abstract class HorizontalThreeDGraph extends HorizontalGridGraph {
+
+ use ThreeDTrait;
+
+ /**
+ * Adjust axes for block spacing, setting the depth unit
+ */
+ protected function adjustAxes(&$x_len, &$y_len)
+ {
+ // make sure project_angle is in range
+ $this->project_angle = min(89, max(1, $this->getOption('project_angle', 30)));
+
+ $ends = $this->getAxisEnds();
+ $bars = $ends['k_max'][0] - $ends['k_min'][0] + 1;
+ $a = deg2rad($this->project_angle);
+
+ $depth = $this->depth;
+ $u = $y_len / ($bars + $depth * cos($a));
+ $c = $u * $depth * cos($a);
+ if($c > $x_len) {
+ // doesn't fit - use 1/2 length
+ $c = $x_len / 2;
+ $u = $d / $depth * cos($a);
+ }
+ $d = $u * $depth * sin($a);
+ $x_len -= $c;
+ $y_len -= $d;
+ $this->depth_unit = $y_len / $bars;
+ return [$c, $d];
+ }
+
+ /**
+ * Returns the (vertical) grid stripes as a string
+ */
+ protected function getGridStripes()
+ {
+ if(!$this->getOption('grid_back_stripe'))
+ return '';
+
+ $z = $this->depth * $this->depth_unit;
+ list($xd,$yd) = $this->project(0, 0, $z);
+ $y_h = new Number($this->g_height);
+ $minus_y_h = new Number(-$this->g_height);
+ $ybottom = new Number($this->height - $this->pad_bottom);
+ $minus_xd = new Number(-$xd);
+ $minus_yd = new Number(-$yd);
+ $xd = new Number($xd);
+ $yd = new Number($yd);
+
+ // use array of colours if available, otherwise stripe a single colour
+ $colours = $this->getOption('grid_back_stripe_colour');
+ if(!is_array($colours))
+ $colours = [null, $colours];
+ $opacity = $this->getOption('grid_back_stripe_opacity');
+
+ $pathdata = '';
+ $c = 0;
+ $p1 = null;
+ $num_colours = count($colours);
+ $points = $this->getGridPointsX($this->main_x_axis);
+ $first = array_shift($points);
+ $last_pos = $first->position;
+ $stripes = '';
+ foreach($points as $x) {
+ $x = $x->position;
+ $cc = $colours[$c % $num_colours];
+ if($cc !== null) {
+ $x1 = $last_pos - $x;
+ $dpath = new PathData('M', $x, $ybottom,
+ 'l', $xd, $yd,
+ 'v', $minus_y_h,
+ 'h', $x1,
+ 'v', $y_h,
+ 'l', $minus_xd, $minus_yd, 'z');
+ $bpath = [
+ 'fill' => new Colour($this, $cc),
+ 'd' => $dpath,
+ ];
+ if($opacity != 1)
+ $bpath['fill-opacity'] = $opacity;
+ $stripes .= $this->element('path', $bpath);
+ } else {
+ $p1 = $x;
+ }
+ $last_pos = $x;
+ ++$c;
+ }
+ return $stripes;
+ }
+}
diff --git a/classes/vendor/81x/goat1000/svggraph/Image.php b/classes/vendor/81x/goat1000/svggraph/Image.php
new file mode 100644
index 0000000..c369558
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/Image.php
@@ -0,0 +1,44 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class Image extends Shape {
+ protected $element = 'image';
+ protected $required = ['src', 'x', 'y'];
+ protected $transform = ['width' => 'x', 'height' => 'y'];
+ protected $transform_from = ['width' => 'x', 'height' => 'y'];
+ protected $transform_pairs = [ ['x', 'y'] ];
+ protected $attrs = ['preserveAspectRatio' => 'xMinYMin'];
+
+ /**
+ * Override to draw an image
+ */
+ protected function drawElement(&$graph, &$attributes)
+ {
+ $attributes['xlink:href'] = $attributes['src'];
+ if(isset($attributes['stretch']) && $attributes['stretch'])
+ $attributes['preserveAspectRatio'] = 'none';
+ unset($attributes['src'], $attributes['stretch']);
+ return parent::drawElement($graph, $attributes);
+ }
+}
+
diff --git a/classes/vendor/81x/goat1000/svggraph/Javascript.php b/classes/vendor/81x/goat1000/svggraph/Javascript.php
new file mode 100644
index 0000000..9b80ad8
--- /dev/null
+++ b/classes/vendor/81x/goat1000/svggraph/Javascript.php
@@ -0,0 +1,686 @@
+.
+ */
+/**
+ * For more information, please contact
+ */
+
+namespace Goat1000\SVGGraph;
+
+class Javascript {
+
+ private $graph;
+ protected $functions = [];
+ protected $variables = [];
+ protected $comments = [];
+ protected $init_functions = [];
+ protected $fader_enabled = false;
+ protected $clickshow_enabled = false;
+
+ private $namespace = '';
+
+ public function __construct(&$graph)
+ {
+ $this->graph =& $graph;
+
+ if($graph->getOption('namespace'))
+ $this->namespace = 'svg:';
+ }
+
+ /**
+ * Adds any number of functions by name
+ */
+ public function addFuncs()
+ {
+ $fns = func_get_args();
+ foreach($fns as $fn) {
+ if(!isset($this->functions[$fn]))
+ $this->addFunction($fn);
+ }
+ }
+
+ /**
+ * Adds a javascript function
+ */
+ public function addFunction($name, $realname = null)
+ {
+ if($realname === null)
+ $realname = $name;
+
+ if(isset($this->functions[$realname]))
+ return true;
+
+ // functions that fit on one line
+ $simple_functions = [
+ 'setattr' =>
+ "function setattr(i,a,v){i.setAttributeNS(null,a,v);return v}\n",
+ 'getE' =>
+ "function getE(i){return document.getElementById(i)}\n",
+ 'newtext' =>
+ "function newtext(c){return document.createTextNode(c)}\n",
+ 'textAttr' =>
+ "function textAttr(e,a){var s=e.getAttributeNS(null,a);return s?s:'';}\n",
+ // round to nearest whole number
+ 'kround' => "function kround(v){return Math.round(v)|0;}\n",
+ // floor function
+ 'kroundDown' => "function kroundDown(v){return v|0;}\n",
+ ];
+ if(isset($simple_functions[$name]))
+ return $this->insertFunction($name, $simple_functions[$name]);
+
+ // functions that only use a template and other functions
+ $template_functions = [
+ 'dateFormat' => [],
+ 'dateStrValueX' => ['dateFormat'],
+ 'dateStrValueY' => ['dateFormat'],
+ 'fading' => [],
+ 'finditem' => [],
+ 'fitRect' => ['setattr'],
+ 'getData' => [],
+ 'keyStrValueX' => [],
+ 'keyStrValueY' => [],
+ 'logStrValueX' => [],
+ 'logStrValueY' => [],
+ 'newel' => ['setattr'],
+ 'strValueX' => [],
+ 'strValueY' => [],
+ 'svgCursorCoords' => ['svgNode'],
+ ];
+ if(isset($template_functions[$name])) {
+ foreach($template_functions[$name] as $dependency)
+ $this->addFunction($dependency);
+ return $this->insertTemplate($name);
+ }
+
+ switch($name)
+ {
+ case 'showhide' :
+ $this->addFunction('setattr');
+ $fn = "function showhide(e,h){setattr(e,'visibility',h?'visible':'hidden');}\n";
+ break;
+
+ case 'tooltip' :
+ $this->addFuncs('getE', 'setattr', 'newel', 'showhide', 'svgNode',
+ 'svgCursorCoords');
+ $this->insertVariable('tooltipOn', '');
+ $opts = ['stroke_width', 'shadow_opacity', 'round', 'padding',
+ 'back_colour', 'offset', 'align'];
+ $vars = [];
+ foreach($opts as $opt) {
+ $vars[$opt] = $this->graph->getOption('tooltip_' . $opt);
+ }
+
+ $round_part = '';
+ $shadow_part = '';
+ $vars['edge_space'] = $vars['stroke_width'];
+ $vars['stroke'] = $this->graph->getOption('tooltip_stroke_colour',
+ 'tooltip_colour', ['@','#000']);
+ if($vars['round'] > 0) {
+ $round = new Number($vars['round'], 'px');
+ $round_part = ',rx:"' . $round . '",ry:"' . $round . '"';
+ }
+
+ if(is_numeric($vars['shadow_opacity'])) {
+ $ttoffs = 2 - $vars['stroke_width'] / 2;
+ $vars['edge_space'] += $ttoffs;
+ $ttoffs = new Number($ttoffs, 'px');
+ $shadow_part = 'shadow = newel("rect",{id:"ttshdw",fill:"#000",' .
+ 'width:"10px",height:"10px",opacity:' .
+ new Number($vars['shadow_opacity']) .
+ ',x:"' . $ttoffs . '",y:"' . $ttoffs . '"';
+ if($round_part !== '')
+ $shadow_part .= $round_part;
+ $shadow_part .= '});';
+ $shadow_part .= 'tt.appendChild(shadow);';
+ }
+
+ $vars['transform_part'] = "setattr(inner, 'transform', 'translate(";
+ switch($vars['align']) {
+ case 'left' :
+ $vars['transform_part'] .= new Number($vars['padding']) . ",0)');";
+ break;
+ case 'right' :
+ $vars['transform_part'] .= "' + (bw - " . new Number($vars['padding']) . ") + ',0)');";
+ break;
+ default:
+ $vars['transform_part'] .= "' + (bw / 2) + ',0)');";
+ }
+
+ $vars['round_part'] = $round_part;
+ $vars['shadow_part'] = $shadow_part;
+ $vars['dpad'] = 2 * $vars['padding'];
+ $vars['back_colour'] = new Colour($this->graph, $vars['back_colour']);
+ $vars['stroke'] = new Colour($this->graph, $vars['stroke']);
+ return $this->insertTemplate('tooltip', $vars);
+
+ case 'texttt' :
+ $this->addFuncs('getE', 'setattr', 'newel', 'newtext');
+ $opts = ['padding', 'colour', 'font', 'font_weight', 'align'];
+ $vars = [];
+ foreach($opts as $opt)
+ $vars[$opt] = $this->graph->getOption('tooltip_' . $opt);
+ $vars['font_size'] = Number::units($this->graph->getOption('tooltip_font_size'));
+ $vars['line_spacing'] = Number::units($this->graph->getOption('tooltip_line_spacing'));
+ $vars['colour'] = new Colour($this->graph, $vars['colour'], false, false);
+ $vars['ttoffset'] = $vars['font_size'] + $vars['padding'];
+ if($vars['line_spacing'] === null || $vars['line_spacing'] < 1)
+ $vars['tty'] = $vars['ttoffset'];
+ else
+ $vars['tty'] = $vars['line_spacing'];
+
+ $anchors = ['left' => 'start', 'right' => 'end'];
+ $vars['anchor'] = isset($anchors[$vars['align']]) ?
+ $anchors[$vars['align']] : 'middle';
+ return $this->insertTemplate('texttt', $vars);
+
+ case 'ttEvent' :
+ $this->addFuncs('finditem');
+ $this->addInitFunction('ttEvent');
+ return $this->insertTemplate('ttEvent');
+
+ case 'popFront' :
+ $this->addFuncs('getE', 'finditem');
+ $this->addInitFunction('popFront');
+ return $this->insertTemplate('popFront');
+
+ case 'clickShowEvent' :
+ if($this->fader_enabled)
+ return $this->fadeAndClick();
+
+ $this->addFuncs('getE', 'finditem', 'setattr');
+ $this->addInitFunction('clickShowEvent');
+ return $this->insertTemplate('clickShowEvent');
+
+ case 'fade' :
+ if($this->clickshow_enabled)
+ return $this->fadeAndClick();
+
+ $this->addFuncs('getE', 'setattr', 'textAttr');
+ $this->addInitFunction('fade');
+ return $this->insertTemplate('fade');
+
+ case 'fadeEventIn' :
+ $this->addFuncs('finditem');
+ $this->addInitFunction('fadeEventIn');
+ return $this->insertTemplate('fadeEventIn');
+
+ case 'fadeEventOut' :
+ $this->addFuncs('finditem');
+ $this->addInitFunction('fadeEventOut');
+ return $this->insertTemplate('fadeEventOut');
+
+ case 'duplicate' :
+ $this->addFuncs('getE', 'newel', 'setattr');
+ $this->addInitFunction('initDups');
+ return $this->insertTemplate('duplicate', ['namespace' => $this->namespace]);
+
+ case 'svgNode' :
+ return $this->insertTemplate('svgNode', ['namespace' => $this->namespace]);
+
+ case 'autoHide' :
+ $this->addFuncs('getE', 'setattr', 'finditem');
+ $this->addInitFunction('autoHide');
+ return $this->insertTemplate('autoHide');
+
+ case 'chEvt' :
+ $this->addInitFunction('chEvt');
+ return $this->insertTemplate('chEvt');
+
+ case 'showCoords' :
+ $this->addFuncs('getE', 'newel', 'newtext', 'getData', 'showhide',
+ 'fitRect', 'textAttr', 'strValueX', 'strValueY');
+
+ // format text for assoc X, assoc Y or x,y
+ $text_format_x = 'fnx(de,x,bb.width,gx,' .
+ 'textAttr(ti,"unitsbx"),textAttr(ti,"unitsx"))';
+ $text_format_y = 'fny(de,bb.height-y,bb.height,gy,' .
+ 'textAttr(ti,"unitsby"),textAttr(ti,"unitsy"))';
+
+ if(!$this->graph->getOption('crosshairs_show_h'))
+ $text_format = $text_format_x;
+ elseif(!$this->graph->getOption('crosshairs_show_v'))
+ $text_format = $text_format_y;
+ else
+ $text_format = $text_format_x . ' + ", " + ' . $text_format_y;
+
+ $pad = max(0, (int)$this->graph->getOption('crosshairs_text_padding'));
+ $space = max(0, (int)$this->graph->getOption('crosshairs_text_space'));
+ $vars = [
+ 'text_format' => $text_format,
+ 'pad' => $pad,
+ // calculate these here to save doing it in JS
+ 'pad_space' => $pad + $space,
+ 'space2' => $space * 2,
+ ];
+ return $this->insertTemplate('showCoords', $vars);
+
+ case 'crosshairs' :
+ $this->addFuncs('chEvt', 'setattr', 'svgNode', 'svgCursorCoords',
+ 'showhide');
+
+ $vars = [ 'show_x' => '', 'show_y' => '', 'show_text' => ''];
+ if($this->graph->getOption('crosshairs_show_text')) {
+ $this->addFunction('showCoords');
+ $vars['show_text'] = 'showCoords(de, x - bb.x, y - bb.y, bb, on);';
+ }
+ if($this->graph->getOption('crosshairs_show_h'))
+ $vars['show_x'] = 'showhide(xc,on);';
+ if($this->graph->getOption('crosshairs_show_v'))
+ $vars['show_y'] = 'showhide(yc,on);';
+ return $this->insertTemplate('crosshairs', $vars);
+
+ case 'dragEvent' :
+ $this->addFuncs('newel', 'getE', 'setattr', 'finditem', 'svgCursorCoords');
+ $this->addInitFunction('dragEvent');
+ return $this->insertTemplate('dragEvent');
+
+ case 'magEvt' :
+ $this->addInitFunction('magEvt');
+ $vars = ['namespace' => $this->namespace];
+ return $this->insertTemplate('magEvt', $vars);
+
+ default :
+ // Trying to add a function that doesn't exist?
+ throw new \Exception('Unknown function "' . $name . '"');
+ }
+
+ $this->insertFunction($realname, $fn);
+ }
+
+ /**
+ * Inserts a Javascript function into the list
+ */
+ public function insertFunction($name, $fn)
+ {
+ $this->functions[$name] = $fn;
+ }
+
+ /**
+ * Inserts a function from a template
+ */
+ public function insertTemplate($name, $vars = null, $realname = null)
+ {
+ if($realname === null)
+ $realname = $name;
+ $file_path = __DIR__ . '/templates/' . $name . '.txt';
+ if(!file_exists($file_path))
+ throw new \Exception('Template [' . $name . '.txt] not found.');
+ $content = file_get_contents($file_path);
+
+ if($vars !== null) {
+ // insert variables into template
+ $content = preg_replace_callback('/{\$([a-z]+):([a-z0-9_]+)}/',
+ function($m) use($vars) {
+ list(, $type, $var) = $m;
+ if(!isset($vars[$var]))
+ throw new \Exception('Variable [' . $var . '] not defined.');
+
+ $value = $vars[$var];
+ if('number' === $type) {
+ if(is_numeric($value))
+ return new Number($value);
+
+ if(is_object($value) && get_class($value) === 'Goat1000\\SVGGraph\\Number')
+ return $value;
+
+ throw new \Exception('Variable [' . $var . '] not numeric, value "' .
+ $value . '".');
+ }
+ return $value; }, $content);
+ }
+
+ $this->insertFunction($realname, $content);
+ }
+
+ /**
+ * Convert hex from regex matched entity to javascript escape sequence
+ */
+ public static function hex2js($m)
+ {
+ return sprintf('\u%04x', base_convert($m[1], 16, 10));
+ }
+
+ /**
+ * Convert decimal from regex matched entity to javascript escape sequence
+ */
+ public static function dec2js($m)
+ {
+ return sprintf('\u%04x', $m[1]);
+ }
+
+ public static function reEscape($string)
+ {
+ // convert XML char entities to JS unicode
+ $string = preg_replace_callback('/([a-f0-9]+);/',
+ 'Goat1000\\SVGGraph\\Javascript::hex2js', $string);
+ $string = preg_replace_callback('/([0-9]+);/',
+ 'Goat1000\\SVGGraph\\Javascript::dec2js', $string);
+ return $string;
+ }
+
+ /**
+ * Adds a Javascript variable
+ * - use $value:$more for assoc
+ * - use NULL:$more for array
+ */
+ public function insertVariable($var, $value, $more = null, $quote = true)
+ {
+ $q = $quote ? '"' : '';
+ if($more === null)
+ $this->variables[$var] = $q . $this->reEscape($value) . $q;
+ elseif($value === null)
+ $this->variables[$var][] = $q . $this->reEscape($more) . $q;
+ else
+ $this->variables[$var][$value] = $q . $this->reEscape($more) . $q;
+ }
+
+ /**
+ * Insert a numeric variable
+ */
+ public function insertNumberVar($var, $value)
+ {
+ $this->variables[$var] = new Number($value);
+ }
+
+ /**
+ * Adds an init function to the list
+ */
+ public function addInitFunction($name)
+ {
+ $this->init_functions[$name] = 1;
+ }
+
+ /**
+ * Insert a comment into the Javascript section - handy for debugging!
+ */
+ public function insertComment($details)
+ {
+ $this->comments[] = $details;
+ }
+
+ /**
+ * Fade and click at the same time requires different functions
+ */
+ private function fadeAndClick()
+ {
+ $this->addFuncs('getE', 'finditem', 'fading', 'textAttr', 'setattr');
+ $this->addInitFunction('clickShowEvent');
+ $this->addInitFunction('fade');
+ $this->insertTemplate('clickShowEvent_fade', null, 'clickShowEvent');
+ $this->insertTemplate('fade_clickShow', null, 'fade');
+ }
+
+ /**
+ * Sets the tooltip for an element
+ */
+ public function setTooltip(&$element, $text, $duplicate = false)
+ {
+ $this->addFuncs('tooltip', 'texttt', 'ttEvent');
+ if(!isset($element['id']))
+ $element['id'] = $this->graph->newID();
+ $this->insertVariable('tips', $element['id'], $text);
+
+ if($duplicate) {
+ $this->addOverlay($element['id'], $this->graph->newID());
+ }
+ }
+
+ /**
+ * Sets click show/hide for an element
+ * If using with fading, this must be used first
+ */
+ public function setClickShow(&$element, $target, $hidden, $duplicate = false)
+ {
+ if(!isset($element['id']))
+ $element['id'] = $this->graph->newID();
+ $id = $duplicate ? $this->graph->newID() : $element['id'];
+ if($duplicate)
+ $this->addOverlay($element['id'], $id);
+
+ $this->addFunction('clickShowEvent');
+ $show = $hidden ? 0 : 1;
+ $this->insertVariable('clickElements', $element['id'], $target);
+ $this->insertVariable('clickMap', $target, $show, false);
+ $this->clickshow_enabled = true;
+ }
+
+ /**
+ * Sets pop to front for $target when mouse over $element
+ */
+ public function setPopFront(&$element, $target, $duplicate = false)
+ {
+ if(!isset($element['id']))
+ $element['id'] = $this->graph->newID();
+ $id = $duplicate ? $this->graph->newID() : $element['id'];
+ if($duplicate)
+ $this->addOverlay($element['id'], $id);
+
+ $this->addFunction('popFront');
+ $this->insertVariable('popfronts', $element['id'], $target);
+ }
+
+ /**
+ * Sets the fader for an element
+ * If using with clickShow, that must be used first
+ */
+ public function setFader(&$element, $in, $out, $target = null,
+ $duplicate = false)
+ {
+ if(!isset($element['id']))
+ $element['id'] = $this->graph->newID();
+ if($target === null)
+ $target = $element['id'];
+ $id = $duplicate ? $this->graph->newID() : $element['id'];
+
+ $this->addFunction('fade');
+ if($in) {
+ $this->addFunction('fadeEventIn');
+ $this->insertNumberVar('fistep', $in);
+ }
+ if($out) {
+ $this->addFunction('fadeEventOut');
+ $this->insertNumberVar('fostep', -$out);
+ }
+ $this->insertVariable('fades', $element['id'],
+ '{id:"' . $target . '",dir:0}', false);
+ $this->insertNumberVar('fstart', $in ? 0 : 1);
+
+ if($duplicate)
+ $this->addOverlay($element['id'], $id);
+ $this->fader_enabled = true;
+ }
+
+ /**
+ * Makes an item draggable
+ */
+ public function setDraggable(&$element)
+ {
+ if(!isset($element['id']))
+ $element['id'] = $this->graph->newID();
+ $this->addFunction('dragEvent');
+ $this->insertVariable('draggable', $element['id'], 0);
+ }
+
+ /**
+ * Makes something auto-hide
+ */
+ public function autoHide(&$element, $hidden_opacity = 0, $full_opacity = 1)
+ {
+ if(!isset($element['id']))
+ $element['id'] = $this->graph->newID();
+ $this->addFunction('autoHide');
+ $this->insertVariable('autohide', $element['id'], 0);
+ $this->insertVariable('ah_opacity', $element['id'], '[' .
+ new Number($hidden_opacity) . ',' .
+ new Number($full_opacity) . ']', false);
+ }
+
+ /**
+ * Adds magnifier
+ */
+ public function magnifier()
+ {
+ $max_mag = 10.0;
+ $min_mag = 1.1;
+ $max_pan = 10.0;
+ $min_pan = 1.1;
+ $mag = (float)$this->graph->getOption('magnify');
+ $pan = (float)$this->graph->getOption('magnify_pan');
+ if($mag <= $min_mag)
+ $mag = $min_mag;
+ elseif($mag > $max_mag)
+ $mag = $max_mag;
+ if($pan <= $min_pan)
+ $pan = $min_pan;
+ elseif($pan > $max_pan)
+ $pan = $max_pan;
+
+ $vars = [
+ 'magnification' => $mag,
+ 'sensitivity' => $pan,
+ 'namespace' => $this->namespace,
+ ];
+ $this->addFuncs('magEvt','svgNode','svgCursorCoords','setattr');
+ $this->insertTemplate('magnifier', $vars);
+ }
+
+ /**
+ * Add an overlaid copy of an element, with opacity of 0
+ */
+ public function addOverlay($from, $to)
+ {
+ $this->addFunction('duplicate');
+
+ // order matters, so clear previous value
+ if(isset($this->variables['dups'][$from]))
+ unset($this->variables['dups'][$from]);
+ $this->insertVariable('dups', $from, $to);
+ }
+
+ /**
+ * Returns the variables (and comments) as Javascript code
+ */
+ public function getVariables()
+ {
+ $variables = '';
+ if(count($this->variables)) {
+ $vlist = [];
+ foreach($this->variables as $name => $value) {
+ $var = $name;
+ if(is_array($value)) {
+ if(isset($value[0]) && isset($value[count($value)-1])) {
+ $var .= '=[' . implode(',', $value) . ']';
+ } else {
+ $vs = [];
+ foreach($value as $k => $v)
+ if($k)
+ $vs[] = $k . ':' . $v;
+
+ $var .= '={' . implode(',', $vs) . '}';
+ }
+ } elseif($value !== null) {
+ $var .= '=' . $value;
+ }
+ $vlist[] = $var;
+ }
+ $variables = 'var ' . implode(', ', $vlist) . ';';
+ }
+ // comments can be stuck with the variables
+ if(count($this->comments)) {
+ foreach($this->comments as $c) {
+ if(!is_string($c))
+ $c = print_r($c, true);
+ $variables .= "\n// " . str_replace("\n", "\n// ", $c);
+ }
+ }
+ return $variables;
+ }
+
+ /**
+ * Returns the functions as Javascript code
+ */
+ public function getFunctions()
+ {
+ $functions = '';
+ if(count($this->functions)) {
+ if(count($this->init_functions)) {
+ $vars = [
+ 'init_funcs' => implode('();', array_keys($this->init_functions)) . '();'
+ ];
+ $this->insertTemplate('init', $vars);
+ }
+ $functions = implode('', $this->functions);
+ }
+
+ return $functions;
+ }
+
+ /**
+ * Returns the onload code to use for the SVG
+ */
+ public function getOnload()
+ {
+ if(count($this->init_functions))
+ return 'init()';
+ return '';
+ }
+
+ /**
+ * Returns all the code to be inserted into
+