Completed
Push — master ( 90072e...c81959 )
by Daniel
11:23
created

YamlWriter::denormaliseMessages()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 25
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 13
nc 4
nop 1
dl 0
loc 25
rs 8.5806
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\i18n\Messages;
4
5
use SilverStripe\Assets\Filesystem;
6
use SilverStripe\i18n\i18n;
7
use Symfony\Component\Yaml\Dumper;
8
use SilverStripe\i18n\Messages\Symfony\ModuleYamlLoader;
9
use LogicException;
10
11
/**
12
 * Write yml files compatible with ModuleYamlLoader
13
 *
14
 * Note: YamlWriter may not correctly denormalise plural strings if writing outside of the
15
 * default locale (en).
16
 *
17
 * @see ModuleYamlLoader
18
 */
19
class YamlWriter implements Writer
20
{
21
    /**
22
     * @var Dumper
23
     */
24
    protected $dumper = null;
25
26
    /**
27
     * @return Dumper
28
     */
29
    protected function getDumper()
30
    {
31
        if (!$this->dumper) {
32
            $this->dumper = new Dumper();
33
            $this->dumper->setIndentation(2);
34
        }
35
        return $this->dumper;
36
    }
37
38
39
    public function write($messages, $locale, $path)
40
    {
41
        // Skip empty entities
42
        if (empty($messages)) {
43
            return;
44
        }
45
46
        // Create folder for lang files
47
        $langFolder = $path . '/lang';
48
        if (!file_exists($langFolder)) {
49
            Filesystem::makeFolder($langFolder);
50
            touch($langFolder . '/_manifest_exclude');
51
        }
52
53
        // De-normalise messages and convert to yml
54
        $content = $this->getYaml($messages, $locale);
55
56
        // Open the English file and write the Master String Table
57
        $langFile = $langFolder . '/' . $locale . '.yml';
58
        if ($fh = fopen($langFile, "w")) {
59
            fwrite($fh, $content);
60
            fclose($fh);
61
        } else {
62
            throw new LogicException("Cannot write language file! Please check permissions of $langFile");
63
        }
64
    }
65
66
    /**
67
     * Explodes [class.key1 => value1, class.key2 => value2] into [class => [ key1 => value1, key2 => value2]]
68
     *
69
     * Inverse of YamlReader::normaliseMessages()
70
     *
71
     * @param array $messages
72
     * @return array
73
     */
74
    protected function denormaliseMessages($messages)
75
    {
76
        // Sort prior to denormalisation
77
        ksort($messages);
78
        $entities = [];
79
        foreach ($messages as $entity => $value) {
80
            // Skip un-namespaced keys
81
            $value = $this->denormaliseValue($value);
82
83
            // Non-nested key
84
            if (strstr($entity, '.') === false) {
85
                $entities[$entity] = $value;
86
                continue;
87
            }
88
89
            // Get key nested within class
90
            list($class, $key) = $this->getClassKey($entity);
91
            if (!isset($entities[$class])) {
92
                $entities[$class] = [];
93
            }
94
95
            $entities[$class][$key] = $value;
96
        }
97
        return $entities;
98
    }
99
100
    /**
101
     * Convert entities array format into yml-ready string / array
102
     *
103
     * @param array|string $value Input value
104
     * @return array|string denormalised value
105
     */
106
    protected function denormaliseValue($value)
107
    {
108
        // Check plural form
109
        $plurals = $this->getPluralForm($value);
110
        if ($plurals) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $plurals of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
111
            return $plurals;
112
        }
113
114
        // Non-plural non-array is already denormalised
115
        if (!is_array($value)) {
116
            return $value;
117
        }
118
119
        // Denormalise from default key
120
        if (!empty($value['default'])) {
121
            return $this->denormaliseValue($value['default']);
122
        }
123
124
        // No value
125
        return null;
126
    }
127
128
    /**
129
     * Get array-plural form for any value
130
     *
131
     * @param array|string $value
132
     * @return array List of plural forms, or empty array if not plural
133
     */
134
    protected function getPluralForm($value)
135
    {
136
        // Strip non-plural keys away
137
        if (is_array($value)) {
138
            $forms = i18n::config()->get('plurals');
139
            $forms = array_combine($forms, $forms);
140
            return array_intersect_key($value, $forms);
141
        }
142
143
        // Parse from string
144
        // Note: Risky outside of en locale.
145
        return i18n::parse_plurals($value);
146
    }
147
148
    /**
149
     * Convert messages to yml ready to write
150
     *
151
     * @param array $messages
152
     * @param string $locale
153
     * @return string
154
     */
155
    public function getYaml($messages, $locale)
156
    {
157
        $entities = $this->denormaliseMessages($messages);
158
        $content = $this->getDumper()->dump([
159
            $locale => $entities
160
        ], 99);
161
        return $content;
162
    }
163
164
    /**
165
     * Determine class and key for a localisation entity
166
     *
167
     * @param string $entity
168
     * @return array Two-length array with class and key as elements
169
     */
170
    protected function getClassKey($entity)
171
    {
172
        $parts = explode('.', $entity);
173
        $class = array_shift($parts);
174
175
        // Ensure the `.ss` suffix gets added to the top level class rather than the key
176
        if (count($parts) > 1 && reset($parts) === 'ss') {
177
            $class .= '.ss';
178
            array_shift($parts);
179
        }
180
        $key = implode('.', $parts);
181
        return array($class, $key);
182
    }
183
}
184