Issues (1)

app/Services/SourceCodeService.php (1 issue)

1
<?php
2
3
/**
4
 * webtrees-mod-translationtool: MyArtJaub Translation Tool Module for webtrees
5
 *
6
 * @package MyArtJaub\Webtrees\Module
7
 * @subpackage TranslationTool
8
 * @author Jonathan Jaubart <[email protected]>
9
 * @copyright Copyright (c) 2020-2023, Jonathan Jaubart
10
 * @license http://www.gnu.org/licenses/gpl.html GNU General Public License, version 3
11
 */
12
13
declare(strict_types=1);
14
15
namespace MyArtJaub\Webtrees\Module\TranslationTool\Services;
16
17
use Composer\Package\PackageInterface;
18
use Fisharebest\Webtrees\Services\ModuleService;
19
use Gettext\Merge;
20
use Gettext\Translations;
21
use Gettext\Scanner\PhpScanner;
22
use Illuminate\Support\Collection;
23
use MyArtJaub\Webtrees\Module\ModuleMyArtJaubInterface;
24
25
/**
26
 * Service for extracting data from the webtrees and modules source code.
27
 */
28
class SourceCodeService
29
{
30
    /**
31
     * Gettext Translations merge strategy to be used - Use Theirs data
32
     * @var int MERGE_STRATEGY_THEIRS
33
     */
34
    private const MERGE_STRATEGY_THEIRS = Merge::HEADERS_OVERRIDE
35
        | Merge::TRANSLATIONS_THEIRS
36
        | Merge::TRANSLATIONS_OVERRIDE
37
        | Merge::EXTRACTED_COMMENTS_THEIRS
38
        | Merge::REFERENCES_THEIRS
39
        | Merge::FLAGS_THEIRS
40
        | Merge::COMMENTS_THEIRS;
41
42
    /**
43
     * I18N functions to be looked for in the code
44
     * @var array<string, string>
45
     */
46
    private const I18N_FUNCTIONS = [
47
        'translate' => 'gettext',
48
        'plural' => 'ngettext',
49
        'translateContext' => 'pgettext'
50
    ];
51
52
    /**
53
     * Lists all paths containing source code to be scanned for translations.
54
     * This contains the MyArtJaub modules's resources folder,
55
     * as well as MyArtJaub modules PSR-4 autoloading paths loaded through Composer
56
     *
57
     * @return Collection<string, array<string>>
58
     */
59
    public function sourceCodePaths(): Collection
60
    {
61
        $paths = app(ModuleService::class)->findByInterface(ModuleMyArtJaubInterface::class)
62
            ->mapWithKeys(function (ModuleMyArtJaubInterface $module): array {
63
                return [$module->name() => [realpath($module->resourcesFolder())]];
64
            });
65
66
        $maj_packages = app(ComposerService::class)->listMyArtJaubPackagesPaths();
67
68
        foreach ($maj_packages as list($maj_package, $psr4_paths)) {
69
            /** @var PackageInterface $maj_package */
70
            $installer_name = $maj_package->getExtra()['installer-name'] ?? '';
71
            $key = $installer_name === '' ? $maj_package->getName() : '_' . $installer_name . '_';
72
            if (count($psr4_paths) > 0) {
73
                $paths->put($key, array_merge($paths->get($key, []), $psr4_paths));
74
            }
75
        }
76
77
        return $paths;
78
    }
79
80
    /**
81
     * Find all strings to be translated in PHP or PHTML files for a set of source code paths
82
     * The returned structure is a associated Collection with:
83
     *      - key: package/domain
84
     *      - value: Gettext Translations object for that domain
85
     *
86
     * @param Collection<string, array<string>> $source_code_paths
87
     * @return Collection<string, Translations>
88
     */
89
    public function findStringsToTranslate(Collection $source_code_paths): Collection
90
    {
91
        $strings_to_translate = new Collection();
92
        foreach ($source_code_paths as $package => $paths) {
93
            $php_files = array();
94
            foreach ($paths as $path) {
95
                $php_files = [...$php_files, ...$this->glob_recursive($path . '/*.php')];
96
                $php_files = [...$php_files, ...$this->glob_recursive($path . '/*.phtml')];
97
            }
98
99
            $php_scanner = new PhpScanner(Translations::create($package));
100
            $php_scanner
101
                ->setFunctions(self::I18N_FUNCTIONS)
102
                ->ignoreInvalidFunctions(true);
103
            $php_scanner->setDefaultDomain($package);
104
            foreach ($php_files as $php_file) {
105
                $php_scanner->scanFile($php_file);
106
            }
107
108
            $strings_to_translate->put(
109
                $package,
110
                $strings_to_translate
111
                    ->get($package, Translations::create())
112
                    ->mergeWith($php_scanner->getTranslations()[$package], self::MERGE_STRATEGY_THEIRS)
113
            );
114
        }
115
        return $strings_to_translate;
116
    }
117
118
    /**
119
     * List all translations defined in MyArtJaub modules
120
     * The returned structure is a associated Collection with:
121
     *      - key: module name
122
     *      - value: array of translations for the module
123
     *
124
     * @param string $language
125
     * @return Collection<string, array<string,string>>
126
     */
127
    public function listMyArtJaubTranslations(string $language): Collection
128
    {
129
        return app(ModuleService::class)->findByInterface(ModuleMyArtJaubInterface::class)
130
            ->mapWithKeys(function (ModuleMyArtJaubInterface $module) use ($language): array {
131
                return [$module->name() => $module->customTranslations($language)];
132
            });
133
    }
134
135
    /**
136
     * Extension of the standard PHP glob function to apply it recursively.
137
     *
138
     * @param string $pattern
139
     * @param int $flags
140
     * @return array<int, string>
141
     * @see glob()
142
     * @phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps
143
     */
144
    protected function glob_recursive(string $pattern, int $flags = 0): array
145
    {
146
        $files = glob($pattern, $flags) ?: [];
147
        $dirs = glob(dirname($pattern) . '/*', GLOB_ONLYDIR | GLOB_NOSORT) ?: [];
148
149
        foreach ($dirs as $dir) {
150
            $files = [...$files, ...$this->glob_recursive($dir . '/' . basename($pattern), $flags)];
151
        }
152
153
        return $files;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $files returns an array which contains values of type array|array<integer,array|mixed> which are incompatible with the documented value type string.
Loading history...
154
    }
155
}
156