Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
75.86% covered (warning)
75.86%
22 / 29
50.00% covered (danger)
50.00%
3 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
AbstractCommand
75.86% covered (warning)
75.86%
22 / 29
50.00% covered (danger)
50.00%
3 / 6
22.56
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 addLineToFile
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 extractTextDomain
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 getLangDir
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
5.03
 createMissingFiles
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
3.33
 processDirectory
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
5
1<?php
2
3/**
4 * Abstract base class for language commands.
5 *
6 * PHP version 8
7 *
8 * Copyright (C) Villanova University 2020.
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License version 2,
12 * as published by the Free Software Foundation.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
22 *
23 * @category VuFind
24 * @package  Console
25 * @author   Demian Katz <demian.katz@villanova.edu>
26 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
27 * @link     https://vufind.org/wiki/development Wiki
28 */
29
30namespace VuFindConsole\Command\Language;
31
32use Symfony\Component\Console\Command\Command;
33use Symfony\Component\Console\Output\OutputInterface;
34use VuFind\I18n\ExtendedIniNormalizer;
35use VuFind\I18n\Translator\Loader\ExtendedIniReader;
36
37use function count;
38use function in_array;
39use function is_callable;
40
41/**
42 * Abstract base class for language commands.
43 *
44 * @category VuFind
45 * @package  Console
46 * @author   Demian Katz <demian.katz@villanova.edu>
47 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
48 * @link     https://vufind.org/wiki/development Wiki
49 */
50abstract class AbstractCommand extends Command
51{
52    /**
53     * Normalizer for .ini files
54     *
55     * @var ExtendedIniNormalizer
56     */
57    protected $normalizer;
58
59    /**
60     * Reader for .ini files
61     *
62     * @var ExtendedIniReader
63     */
64    protected $reader;
65
66    /**
67     * Language directory
68     *
69     * @var string
70     */
71    protected $languageDir;
72
73    /**
74     * Files to ignore when processing directories
75     *
76     * @var string[]
77     */
78    protected $filesToIgnore = ['aliases.ini', 'native.ini'];
79
80    /**
81     * Constructor
82     *
83     * @param ExtendedIniNormalizer $normalizer  Normalizer for .ini files
84     * @param ExtendedIniReader     $reader      Reader for .ini files
85     * @param string                $languageDir Base language file directory
86     * @param string|null           $name        The name of the command; passing
87     * null means it must be set in configure()
88     */
89    public function __construct(
90        ExtendedIniNormalizer $normalizer = null,
91        ExtendedIniReader $reader = null,
92        $languageDir = null,
93        $name = null
94    ) {
95        $this->normalizer = $normalizer ?? new ExtendedIniNormalizer();
96        $this->reader = $reader ?? new ExtendedIniReader();
97        $this->languageDir = $languageDir
98            ?? realpath(__DIR__ . '/../../../../../../languages');
99        parent::__construct($name);
100    }
101
102    /**
103     * Add a line to a language file
104     *
105     * @param string $filename File to update
106     * @param string $key      Name of language key
107     * @param string $value    Value of translation
108     *
109     * @return void
110     */
111    protected function addLineToFile($filename, $key, $value)
112    {
113        $fHandle = fopen($filename, 'a');
114        if (!$fHandle) {
115            throw new \Exception('Cannot open ' . $filename . ' for writing.');
116        }
117        fwrite($fHandle, "\n$key = \"" . $value . "\"\n");
118        fclose($fHandle);
119    }
120
121    /**
122     * Extract a text domain and key from a raw language key.
123     *
124     * @param string $raw Raw language key
125     *
126     * @return array [textdomain, key]
127     */
128    protected function extractTextDomain($raw)
129    {
130        $parts = explode('::', $raw, 2);
131        return count($parts) > 1 ? $parts : ['default', $raw];
132    }
133
134    /**
135     * Open the language directory as an object using dir(). Return false on
136     * failure.
137     *
138     * @param OutputInterface $output          Output object
139     * @param string          $domain          Text domain to retrieve.
140     * @param bool            $createIfMissing Should we create a missing directory?
141     *
142     * @return object|bool
143     */
144    protected function getLangDir(
145        OutputInterface $output,
146        $domain = 'default',
147        $createIfMissing = false
148    ) {
149        $subDir = $domain == 'default' ? '' : ('/' . $domain);
150        $langDir = $this->languageDir . $subDir;
151        if ($createIfMissing && !is_dir($langDir)) {
152            mkdir($langDir);
153        }
154        $dir = dir(realpath($langDir));
155        if (!$dir) {
156            $output->writeln("Could not open directory $langDir");
157            return false;
158        }
159        return $dir;
160    }
161
162    /**
163     * Create empty files if they do not already exist.
164     *
165     * @param string $path  Directory path
166     * @param array  $files Filenames to create in directory
167     *
168     * @return void
169     */
170    protected function createMissingFiles($path, $files)
171    {
172        foreach ($files as $file) {
173            if (!file_exists($path . '/' . $file)) {
174                file_put_contents($path . '/' . $file, '');
175            }
176        }
177    }
178
179    /**
180     * Process a language directory.
181     *
182     * @param object   $dir            Directory object from dir() to process
183     * @param callable $callback       Function to run on all .ini files in $dir
184     * @param bool     $statusCallback Callback function to display status messages
185     * (omit to suppress messages)
186     *
187     * @return void
188     */
189    protected function processDirectory($dir, $callback, $statusCallback = false)
190    {
191        while ($file = $dir->read()) {
192            // Only process .ini files, and ignore special case files:
193            if (str_ends_with($file, '.ini') && !in_array($file, $this->filesToIgnore)) {
194                if (is_callable($statusCallback)) {
195                    $statusCallback("Processing $file...");
196                }
197                $callback($dir->path . '/' . $file);
198            }
199        }
200    }
201}