Skip to content

[ENHANCEMENT] Integrate Yasumi library for automatic public holiday import by country #55

@giovanny07

Description

@giovanny07

Plugin version: 3.2.6
GLPI version: 11.x
Priority: Medium
File: src/PublicHoliday.php, src/Config.php

Summary

Currently, public holidays must be entered manually into the plugin. The holiday plugin by Teclib already solves this using [Yasumi](https://github.com/azuyalabs/yasumi), a PHP library that provides public holidays for 50+ countries and regions. Adding Yasumi to Activity would allow administrators to bulk-import national holidays with a single action, eliminating manual data entry.

Proposed feature

Add a new section in Activity's configuration page: "Import public holidays" with:

  • A country dropdown (populated from Yasumi providers).
  • A year range selector.
  • A checkbox to clear existing public holidays before importing.
  • An import button.

Implementation

Step 1 — Add Yasumi to composer.json

{
    "require": {
        "fpdf/fpdf": "^1.86",
        "azuyalabs/yasumi": "^2.7"
    }
}

Step 2 — Add import method to PublicHoliday (or a new PublicHolidayImporter class)

// src/PublicHolidayImporter.php
namespace GlpiPlugin\Activity;

use Yasumi\Yasumi;
use Yasumi\Holiday as YasumiHoliday;

class PublicHolidayImporter
{
    /**
     * Import public holidays for a given country and year range.
     *
     * @param string $countryCode  Yasumi provider key, e.g. 'France', 'Colombia'
     * @param int    $startYear
     * @param int    $endYear
     * @param bool   $clearExisting  If true, removes all existing public holidays first
     * @return array{imported: int, skipped: int}
     */
    public static function import(
        string $countryCode,
        int $startYear,
        int $endYear,
        bool $clearExisting = false
    ): array {
        global $DB, $CFG_GLPI;

        if ($clearExisting) {
            $DB->delete('glpi_plugin_activity_publicholidays', [true]);
        }

        $imported = 0;
        $skipped  = 0;

        for ($year = $startYear; $year <= $endYear; $year++) {
            $holidays = Yasumi::create($countryCode, $year);

            foreach ($holidays->getHolidays() as $holiday) {
                $date  = $holiday->format('Y-m-d');
                $label = $holiday->getName([
                    $CFG_GLPI['language'],
                    'en_GB',
                    YasumiHoliday::LOCALE_KEY,
                ]);

                // Skip if already exists for this date
                if (countElementsInTable(
                    'glpi_plugin_activity_publicholidays',
                    ['date' => $date]
                ) > 0) {
                    $skipped++;
                    continue;
                }

                $publicHoliday = new PublicHoliday();
                $publicHoliday->add([
                    'name'        => $label . " ($year)",
                    'date'        => $date,
                    'entities_id' => 0,
                    'is_recursive' => 1,
                ]);

                $imported++;
            }
        }

        return ['imported' => $imported, 'skipped' => $skipped];
    }

    /**
     * Return all available Yasumi country providers as a key=>label array.
     */
    public static function getCountryProviders(): array
    {
        $providers = Yasumi::getProviders();
        return array_combine(
            array_map('strval', $providers),
            $providers
        );
    }
}

Step 3 — Add import form to Config

// src/Config.php — add import section to showForm()
// Or as a new Twig template section:
{# templates/config_publicholidays.html.twig #}
{% import 'components/form/fields_macros.html.twig' as fields %}

<div class="hr-text">
    <i class="fa-2x ti ti-calendar-event"></i>
    <span>{{ __('Import public holidays', 'activity') }}</span>
</div>

<form action="{{ path('front/config.form.php') }}" method="post">
    <input type="hidden" name="_glpi_csrf_token" value="{{ csrf_token() }}" />
    <input type="hidden" name="import_public_holidays" value="1" />

    {{ fields.dropdownArrayField(
        'country',
        preselected_country,
        countries,
        __('Country', 'activity'),
        { 'display_emptychoice': true }
    ) }}

    {{ fields.numberField(
        'start_year',
        current_year,
        __('From year', 'activity'),
        { 'min': 2000, 'max': current_year + 5 }
    ) }}

    {{ fields.numberField(
        'end_year',
        current_year + 1,
        __('To year', 'activity'),
        { 'min': 2000, 'max': current_year + 5 }
    ) }}

    {{ fields.checkboxField(
        'clear_existing',
        0,
        __('Clear existing public holidays before import', 'activity')
    ) }}

    <div class="hstack gap-1 mt-3">
        <div class="ms-auto">
            <button type="submit" class="btn btn-primary">
                <i class="ti ti-download"></i>
                {{ __('Import', 'activity') }}
            </button>
        </div>
    </div>
</form>

Step 4 — Handle import in front/config.form.php

// front/config.form.php
if (isset($_POST['import_public_holidays'])) {
    Session::checkRight('plugin_activity', UPDATE);

    $result = \GlpiPlugin\Activity\PublicHolidayImporter::import(
        $_POST['country'],
        (int) $_POST['start_year'],
        (int) $_POST['end_year'],
        (bool) ($_POST['clear_existing'] ?? false)
    );

    Session::addMessageAfterRedirect(
        sprintf(
            __('%d holidays imported, %d skipped (already exist).', 'activity'),
            $result['imported'],
            $result['skipped']
        ),
        true
    );

    Html::back();
}

Notes

  • Yasumi supports 50+ countries including Colombia (Colombia — to be added to Yasumi if not yet present), France, Spain, Germany, USA, Brazil, etc.
  • The holiday plugin by Teclib already uses Yasumi — if both plugins are installed, consider a shared utility to avoid duplicate imports.
  • Unlike the Teclib holiday plugin (which imports into GLPI's native glpi_holidays SLA table), this feature imports into Activity's own glpi_plugin_activity_publicholidays table for use in the Activity planning view and day-count calculations.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions