Test Failed
Pull Request — master (#9)
by
unknown
07:45
created

LocaleAwareTrait::parseLocale()   B

Complexity

Conditions 6
Paths 18

Size

Total Lines 34

Duplication

Lines 12
Ratio 35.29 %

Importance

Changes 0
Metric Value
dl 12
loc 34
rs 8.7537
c 0
b 0
f 0
cc 6
nc 18
nop 2
1
<?php
2
3
namespace Charcoal\Cms\Support;
4
5
use InvalidArgumentException;
6
7
// From 'psr/http-message'
8
use Psr\Http\Message\UriInterface;
9
10
// From 'charcoal-object'
11
use Charcoal\Object\RoutableInterface;
12
13
/**
14
 * Provides awareness of locales.
15
 *
16
 * ## Requirements
17
 *
18
 * - Translator (e.g., {@see \Charcoal\Translator\TranslatorAwareTrait})
19
 */
20
trait LocaleAwareTrait
21
{
22
    /**
23
     * Available languages as defined by the application configset.
24
     *
25
     * @var array
26
     */
27
    protected $locales = [];
28
29
    /**
30
     * Store the processed link structures to translations
31
     * for the current route, if any.
32
     *
33
     * @var array
34
     */
35
    protected $alternateTranslations;
36
37
    /**
38
     * Retrieve the available locales.
39
     *
40
     * @return array
41
     */
42
    protected function locales()
43
    {
44
        return $this->locales;
45
    }
46
47
    /**
48
     * Set the available locales.
49
     *
50
     * @param  array $locales The list of language structures.
51
     * @return self
52
     */
53
    protected function setLocales(array $locales)
54
    {
55
        $this->locales = [];
56
        foreach ($locales as $langCode => $localeStruct) {
57
            $this->locales[$langCode] = $this->parseLocale($localeStruct, $langCode);
58
        }
59
60
        return $this;
61
    }
62
63
    /**
64
     * Parse the given locale.
65
     *
66
     * @see    \Charcoal\Admin\Widget\FormSidebarWidget::languages()
67
     * @see    \Charcoal\Admin\Widget\FormGroupWidget::languages()
68
     * @param  array  $localeStruct The language structure.
69
     * @param  string $langCode     The language code.
70
     * @throws InvalidArgumentException If the locale does not have a language code.
71
     * @return array
72
     */
73
    private function parseLocale(array $localeStruct, $langCode)
74
    {
75
        $trans = 'locale.' . $langCode;
76
77
        /** Setup the name of the language in the current locale */
78
        if (isset($localeStruct['name'])) {
79
            $name = $this->translator()->translate($localeStruct['name']);
80 View Code Duplication
        } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
81
            $name = $this->translator()->translate($trans);
82
            if ($trans === $name) {
83
                $name = strtoupper($langCode);
84
            }
85
        }
86
87
        /** Setup the native name of the language */
88
        if (isset($localeStruct['native'])) {
89
            $native = $this->translator()->translate($localeStruct['native'], [], null, $langCode);
90 View Code Duplication
        } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
91
            $native = $this->translator()->translate($trans, [], null, $langCode);
92
            if ($trans === $native) {
93
                $native = strtoupper($langCode);
94
            }
95
        }
96
97
        if (!isset($localeStruct['locale'])) {
98
            $localeStruct['locale'] = $langCode;
99
        }
100
101
        $localeStruct['name']   = $name;
102
        $localeStruct['native'] = $native;
103
        $localeStruct['code']   = $langCode;
104
105
        return $localeStruct;
106
    }
107
108
    /**
109
     * Retrieve the translator service.
110
     *
111
     * @return array
112
     */
113
    protected function availableLanguages()
114
    {
115
        return array_keys($this->availableLocales);
0 ignored issues
show
Bug introduced by
The property availableLocales does not seem to exist. Did you mean locales?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
116
    }
117
118
    /**
119
     * Build the alternate translations associated with the current route.
120
     *
121
     * This method _excludes_ the current route's canonical URI.
122
     *
123
     * @return array
124
     */
125
    protected function buildAlternateTranslations()
126
    {
127
        $translations = [];
128
129
        $context  = $this->contextObject();
130
        $origLang = $this->currentLanguage();
0 ignored issues
show
Bug introduced by
It seems like currentLanguage() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
131
132
        $this->translator()->isIteratingLocales = true;
0 ignored issues
show
Bug introduced by
The property isIteratingLocales does not seem to exist. Did you mean locale?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
133
        foreach ($this->locales() as $langCode => $localeStruct) {
134
            if ($langCode === $origLang) {
135
                continue;
136
            }
137
138
            $this->translator()->setLocale($langCode);
139
140
            $translations[$langCode] = $this->formatAlternateTranslation($context, $localeStruct);
141
        }
142
143
        $this->translator()->setLocale($origLang);
144
        unset($this->translator()->isIteratingLocales);
145
146
        return $translations;
147
    }
148
149
    /**
150
     * Retrieve the alternate translations associated with the current route.
151
     *
152
     * This method _excludes_ the current route's canonical URI.
153
     *
154
     * @return array
155
     */
156
    protected function getAlternateTranslations()
157
    {
158
        if ($this->alternateTranslations === null) {
159
            $this->alternateTranslations = $this->buildAlternateTranslations();
160
        }
161
162
        return $this->alternateTranslations;
163
    }
164
165
    /**
166
     * Format an alternate translation for the given translatable model.
167
     *
168
     * Note: The application's locale is already modified and will be reset
169
     * after processing all available languages.
170
     *
171
     * @param  mixed $context      The translated {@see \Charcoal\Model\ModelInterface model}
172
     *     or array-accessible structure.
173
     * @param  array $localeStruct The currently iterated language.
174
     * @return array Returns a link structure.
175
     */
176
    protected function formatAlternateTranslation($context, array $localeStruct)
177
    {
178
        return [
179
            'id'       => ($context['id']) ? : $this->templateName(),
180
            'title'    => ((string)$context['title']) ? : $this->title(),
181
            'url'      => $this->formatAlternateTranslationUrl($context, $localeStruct),
182
            'hreflang' => $localeStruct['code'],
183
            'locale'   => $localeStruct['locale'],
184
            'name'     => $localeStruct['name'],
185
            'native'   => $localeStruct['native'],
186
        ];
187
    }
188
189
    /**
190
     * Format an alternate translation URL for the given translatable model.
191
     *
192
     * Note: The application's locale is already modified and will be reset
193
     * after processing all available languages.
194
     *
195
     * @param  mixed $context      The translated {@see \Charcoal\Model\ModelInterface model}
196
     *     or array-accessible structure.
197
     * @param  array $localeStruct The currently iterated language.
198
     * @return string Returns a link.
199
     */
200
    protected function formatAlternateTranslationUrl($context, array $localeStruct)
201
    {
202
        $isRoutable = ($context instanceof RoutableInterface && $context->isActiveRoute());
203
        $langCode   = $localeStruct['code'];
204
        $path       = ($isRoutable ? $context->url($langCode) : ($this->currentUrl() ? : $langCode));
0 ignored issues
show
Unused Code introduced by
The call to RoutableInterface::url() has too many arguments starting with $langCode.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
205
206
        if ($path instanceof UriInterface) {
207
            $path = $path->getPath();
208
        }
209
210
        return $this->baseUrl()->withPath($path);
211
    }
212
213
    /**
214
     * Yield the alternate translations associated with the current route.
215
     *
216
     * @return Generator|null
217
     */
218
    public function alternateTranslations()
219
    {
220
        foreach ($this->getAlternateTranslations() as $langCode => $transStruct) {
221
            yield $langCode => $transStruct;
222
        }
223
    }
224
225
    /**
226
     * Determine if there exists alternate translations associated with the current route.
227
     *
228
     * @return boolean
229
     */
230
    public function hasAlternateTranslations()
231
    {
232
        return !empty($this->getAlternateTranslations());
233
    }
234
235
    /**
236
     * Retrieve the translator service.
237
     *
238
     * @see    \Charcoal\Translator\TranslatorAwareTrait
239
     * @return \Charcoal\Translator\Translator
240
     */
241
    abstract protected function translator();
242
243
    /**
244
     * Retrieve the template's identifier.
245
     *
246
     * @return string
247
     */
248
    abstract public function templateName();
249
250
    /**
251
     * Retrieve the title of the page (from the context).
252
     *
253
     * @return string
254
     */
255
    abstract public function title();
256
257
    /**
258
     * Retrieve the current URI of the context.
259
     *
260
     * @return \Psr\Http\Message\UriInterface|string
261
     */
262
    abstract public function currentUrl();
263
264
    /**
265
     * Retrieve the current object relative to the context.
266
     *
267
     * @return \Charcoal\Model\ModelInterface|null
268
     */
269
    abstract public function contextObject();
270
271
    /**
272
     * Retrieve the base URI of the project.
273
     *
274
     * @throws RuntimeException If the base URI is missing.
275
     * @return UriInterface|null
276
     */
277
    abstract public function baseUrl();
278
}
279