Completed
Push — development ( eb9524...db4517 )
by Andrij
28:49 queued 02:09
created

MetaManipulator::transliteration()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 6
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
1
<?php
2
3
namespace CMSFactory\MetaManipulator;
4
5
use CI;
6
use CMSFactory\assetManager;
7
use Currency\Currency;
8
use Exception;
9
use MY_Controller;
10
use phpMorphy;
11
use phpMorphy_Exception;
12
use SBrands;
13
use SCategory;
14
use SProducts;
15
16
/**
17
 * Class MetaManipulator
18
 * @package CMSFactory\MetaManipulator
19
 */
20
class MetaManipulator
21
{
0 ignored issues
show
introduced by
Opening brace of a class must be on the same line as the definition
Loading history...
22
    const META_TITLE = 1;
23
    const META_DESCRIPTION = 2;
24
    const META_KEYWORDS = 3;
25
    const META_H1 = 4;
26
27
    /**
28
     * Currency
29
     * @var string
30
     */
31
    protected $CS;
32
33
    /**
34
     * ID
35
     * @var string
36
     */
37
    protected $ID;
38
39
    /**
40
     * Brand
41
     * @var string
42
     */
43
    protected $brand;
44
45
    /**
46
     * Category
47
     * @var string
48
     */
49
    protected $category;
50
51
    /**
52
     * @var int
53
     */
54
    protected $descLength;
55
56
    /**
57
     * @var string
58
     */
59
    protected $description;
60
61
    /**
62
     * @var array
63
     */
64
    protected $matching = [];
65
66
    /**
67
     * @var array
68
     */
69
    protected $metaArray = [];
70
71
    /**
72
     * @var string
73
     */
74
    protected $metaDescription;
75
76
    /**
77
     * @var string
78
     */
79
    protected $metaH1;
80
81
    /**
82
     * @var string
83
     */
84
    protected $metaKeywords;
85
86
    /**
87
     * @var string
88
     */
89
    protected $metaTitle;
90
91
    /**
92
     * @var SCategory|SBrands|SProducts|array|null
93
     */
94
    protected $model;
95
96
    /**
97
     * @var array
98
     */
99
    protected $morph = [];
100
101
    /**
102
     * @var string
103
     */
104
    protected $name;
105
106
    /**
107
     * @var int
108
     */
109
    protected $number;
110
111
    /**
112
     * @var string
113
     */
114
    protected $pageNumber;
115
116
    /**
117
     * Price
118
     * @var string
119
     */
120
    protected $price;
121
122
    /**
123
     * @var MetaStorage
124
     */
125
    protected $storage;
126
127
    /**
128
     * @var string
129
     */
130
    protected $wrapper = '%';
131
132
    /**
133
     * @var array
134
     */
135
    private $graments = ['ИМ', 'РД', 'ДТ', 'ВН', 'ТВ', 'ПР'];
136
137
    /**
138
     * method processing meta data
139
     * @var
140
     */
141
    private $phpMorphy;
142
143
    /**
144
     * MetaManipulator constructor.
145
     * @param SBrands|SCategory|SProducts|array $model
146
     * @param MetaStorage $storage
147
     * @throws Exception
0 ignored issues
show
introduced by
Comment missing or not on the next line for @throws tag in function comment
Loading history...
148
     */
149
    public function __construct($model, MetaStorage $storage) {
150
151
        $dir = APPPATH . 'modules/CMSFactory/MetaManipulator/dics';
152
153
        // set some options
154
        $opts = [
155
            // storage type, follow types supported
156
            // PHPMORPHY_STORAGE_FILE - use file operations(fread, fseek) for dictionary access, this is very slow...
157
            // PHPMORPHY_STORAGE_SHM - load dictionary in shared memory(using shmop php extension), this is preferred mode
158
            // PHPMORPHY_STORAGE_MEM - load dict to memory each time when phpMorphy initialized, this useful when shmop ext. not activated.
159
            //                          Speed same as for PHPMORPHY_STORAGE_SHM type
160
            'storage' => extension_loaded('shmop') ? PHPMORPHY_STORAGE_SHM : PHPMORPHY_STORAGE_MEM,
161
            // Enable prediction by suffix
162
            'predict_by_suffix' => true,
163
            // Enable prediction by prefix
164
            'predict_by_db' => true,
165
            'graminfo_as_text' => true,
166
        ];
167
168
        $this->setPhpMorphy(new phpMorphy($dir, 'ru_RU', $opts));
169
170
        if ($model === null) {
171
            throw new Exception('Model not set');
172
        }
173
174
        if ($storage === null) {
175
            throw new Exception('Storage not set');
176
        }
177
178
        $this->setModel($model);
179
        $this->setStorage($storage);
180
        $this->setMetaTitle($this->getStorage()->getTitleTemplate());
181
        $this->setMetaDescription($this->getStorage()->getDescriptionTemplate());
182
        $this->setMetaKeywords($this->getStorage()->getKeywordsTemplate());
183
        $this->setMetaH1($this->getStorage()->getH1Template());
184
185
        $this->setMatching('desc', 'description');
186
        $this->setMetaArray(['metaTitle', 'metaH1', 'metaDescription', 'metaKeywords']);
187
    }
188
189
    /**
190
     * @return array
191
     */
192
    public function getGraments() {
193
194
        return $this->graments;
195
    }
196
197
    /**
198
     * @return MetaStorage
199
     */
200
    public function getStorage() {
201
202
        return $this->storage;
203
    }
204
205
    /**
206
     * @param MetaStorage $storage
207
     */
208
    public function setStorage($storage) {
209
210
        $this->storage = $storage;
211
    }
212
213
    /**
214
     * @param string $name
215
     * @param string $arguments
216
     * @return string
217
     */
218
    public function __call($name, $arguments) {
219
220
        $prev_string = $name;
221
        $method = substr($name, 0, strpos($name, '['));
222
        $string = method_exists(__CLASS__, $method) ? $this->$method() : '';
223
224
        return $string !== '' ? $this->make($string, $prev_string) : '';
225
    }
226
227
    /**
228
     * @param string $string
229
     * @param string $prev_string
230
     * @return null|string
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use string.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
231
     */
232
    public function make($string, $prev_string) {
233
234
        $return = $string;
235
236
        //morphing
237
        if (preg_match('/\[\d\]/', $prev_string, $match)) {
238
            $part = str_replace(['[', ']'], '', $match[0]);
239
            $words = explode(' ', $string);
240
            foreach ($words as $word) {
241
                $array[] = $this->morphing($word, $part);
0 ignored issues
show
Coding Style Comprehensibility introduced by
$array was never initialized. Although not strictly required by PHP, it is generally a good practice to add $array = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
242
            }
243
            $return = implode(' ', $array);
244
        }
245
246
        //transliteration
247
        if (preg_match('/\[t\]/', $prev_string)) {
248
            $return = $this->transliteration($return);
249
        }
250
251
        return $return;
252
    }
253
254
    /**
255
     * @param string $string
256
     * @param int $part 1-6
257
     * @return string|null
258
     */
259
    protected function morphing($string, $part) {
260
261
        $ucFirst = false;
262
        //check if first letter is uppercase
263
        if (mb_strtolower($string) !== $string) {
264
            $ucFirst = true;
265
        }
266
267
        $word = mb_strtoupper($string);
268
269
        if (!array_key_exists($string, $this->morph)) {
270
            try {
271
272
                if (function_exists('iconv')) {
273
                    $word = iconv('utf-8', $this->getPhpMorphy()->getEncoding(), $word);
274
                }
275
                $collection = $this->getPhpMorphy()->findWord($word);
276
277
                if (false !== $collection) {
278
                    $param = $this->getTypeMany($word);
279
280
                    foreach ($collection->getByPartOfSpeech($param['TypeSpeech']) as $paradigm) {
281
282
                        $checkGrammat = $this->checkGrammat($paradigm, $param, $word);
283
284
                        $result = $this->getGrammensWord($paradigm, $param, $checkGrammat);
0 ignored issues
show
Documentation introduced by
$param is of type array<string,string|arra...,"TypeSpeech":"array"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
285
286
                        if ($result[0] == $word) {
287
                            break;
288
                        }
289
                    }
290
291
                    $this->setMorph($string, $result);
292
                }
293
                return $this->getMorph($string, $part, $ucFirst);
294
            } catch (phpMorphy_Exception $e) {
295
                die('Error occurred while creating phpMorphy instance: ' . PHP_EOL . $e->getMessage());
296
            }
297
        }
298
299
        return $this->getMorph($string, $part, $ucFirst);
300
    }
301
302
    /**
303
     * @param object $paradigm
304
     * @param  string $param
305
     * @param null|string $gramat
306
     * @return array
307
     */
308
    private function getGrammensWord($paradigm, $param, $gramat = null) {
309
310
        $result = [];
311
        if ($gramat != null) {
312
313 View Code Duplication
            foreach ($this->getGraments() as $key => $val) {
314
                foreach ($paradigm->getWordFormsByGrammems([$param['param'], $val , $gramat]) as $form) {
0 ignored issues
show
Coding Style introduced by
Expected 0 spaces between "$val" and comma; 1 found
Loading history...
315
                    if (!$result[$key]) {
316
                        $result[$key] = $form->getWord();
317
                    }
318
                }
319
            }
320
        } else {
321 View Code Duplication
            foreach ($this->getGraments() as $key => $val) {
322
                foreach ($paradigm->getWordFormsByGrammems([$param['param'], $val]) as $form) {
323
                    if (!$result[$key]) {
324
                        $result[$key] = $form->getWord();
325
                    }
326
                }
327
            }
328
        }
329
        return $result;
330
    }
331
332
    /**
333
     * @param string $word
334
     * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,string|array>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
335
     */
336
    private function getTypeMany($word) {
337
338
        $checkString = $this->getPhpMorphy()->castFormByGramInfo($word, null, ['МН', 'ИМ'], true)[0];
339
340
        $param = ($word != $checkString) ? 'ЕД' : 'МН';
341
342
        $typeSpeech = $this->getTypeSpeechWord($word, $param);
343
344
        $result = [
345
            'param' => $param,
346
            'TypeSpeech' => $typeSpeech
347
        ];
348
349
        return $result;
350
    }
351
352
    /**
353
     * @param string $word
354
     * @param string $param
355
     * @return array
356
     */
357
    private function getTypeSpeechWord($word, $param) {
358
359
        $typeSpeech = $this->getPhpMorphy()->getPartOfSpeech($word);
360
        foreach ($typeSpeech as $type) {
361
            $checkGrammar[$type] = $this->getPhpMorphy()->castFormByGramInfo($word, $type, [$param, 'ИМ'], true);
0 ignored issues
show
Coding Style Comprehensibility introduced by
$checkGrammar was never initialized. Although not strictly required by PHP, it is generally a good practice to add $checkGrammar = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
362
363
            foreach ($checkGrammar[$type] as $value) {
364
365
                if (empty($resultType)) {
366
                    $resultType = $word == $value ? $type : null;
367
                }
368
                if ($resultType) {
369
                    break 2;
370
                }
371
            }
372
        }
373
374
        return $resultType;
375
376
    }
377
378
    /**
379
     * @param object $paradigm
380
     * @param array $param
381
     * @param string $word
382
     * @return string|null
383
     */
384
    private function checkGrammat($paradigm, $param, $word) {
385
386
        foreach ($paradigm as $form) {
387
            foreach ($form->getGrammems() as $grammatical) {
388
                switch ($grammatical) {
389
                    case 'МР':
390
                    case 'ЖР':
391
                    case 'СР':
392
                        if (empty($checkGrammar)) {
393
394
                            $checkGrammar = ($word == $this->getPhpMorphy()->castFormByGramInfo($word, $param['TypeSpeech'], [$param['param'], 'ИМ', $grammatical], true)[0]) ? $grammatical : null;
395
                        }
396
                        if ($checkGrammar) {
397
                            continue;
398
                        }
399
400
                }
401
            }
402
        }
403
404
        return $checkGrammar;
405
406
    }
407
408
    /**
409
     * @param phpMorphy $phpMorphy
410
     */
411
    private function setPhpMorphy($phpMorphy) {
412
413
        $this->phpMorphy = $phpMorphy;
414
    }
415
416
    /**
417
     * @return phpMorphy
418
     */
419
    private function getPhpMorphy() {
420
421
        return $this->phpMorphy;
422
    }
423
424
    /**
425
     * @param string $string
426
     * @param integer $part
427
     * @param bool $ucFirst
428
     * @return array
0 ignored issues
show
Documentation introduced by
Should the return type not be string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
429
     */
430
    public function getMorph($string, $part, $ucFirst = false) {
431
432
        if (array_key_exists(--$part, $this->morph[$string])) {
433
            $string = mb_strtolower($this->morph[$string][$part]);
434
        }
435
        return $ucFirst ? mb_strtoupper(mb_substr($string, 0, 1)) . mb_substr($string, 1) : $string;
436
    }
437
438
    /**
439
     * @param string $string
440
     * @param array $morph
441
     */
442
    public function setMorph($string, $morph) {
443
444
        $this->morph[$string] = $morph ?: $string;
445
    }
446
447
    /**
448
     * @param string $string
449
     * @return string
450
     */
451
    protected function transliteration($string = '') {
452
453
        CI::$APP->load->helper('translit');
454
455
        return translit($string);
456
    }
457
458
    /**
459
     * @return string
460
     */
461
    public function getBrand() {
462
463
        return $this->brand;
464
    }
465
466
    /**
467
     * @param string $brand
468
     */
469
    public function setBrand($brand) {
470
471
        $this->brand = $brand;
472
    }
473
474
    /**
475
     * @return string
476
     */
477
    public function getCS() {
478
479
        if (!$this->CS) {
480
            $this->setCS(Currency::create()->getSymbol());
481
        }
482
        return $this->CS;
483
    }
484
485
    /**
486
     * @param string $CS
487
     */
488
    public function setCS($CS) {
489
490
        $this->CS = $CS;
491
    }
492
493
    /**
494
     * @return string
495
     */
496
    public function getCategory() {
497
498
        return $this->category;
499
    }
500
501
    /**
502
     * @param string $category
503
     */
504
    public function setCategory($category) {
505
506
        $this->category = $category;
507
    }
508
509
    /**
510
     * @return int
511
     */
512
    public function getDescLength() {
513
514
        if ($this->descLength === 0) {
515
            return 0;
516
        } elseif ($this->descLength > 0) {
517
            return $this->descLength;
518
        } else {
519
            return 100;
520
        }
521
    }
522
523
    /**
524
     * @param int $descLength
525
     */
526
    public function setDescLength($descLength) {
527
528
        if ($descLength != null and (int) $descLength >= 0) {
529
            $this->descLength = (int) $descLength;
530
        }
531
    }
532
533
    /**
534
     * @return string
535
     */
536
    public function getDescription() {
537
538
        if (!$this->description) {
539
            $this->setDescription($this->getModel()->getDescription());
540
        }
541
        return $this->description;
542
    }
543
544
    /**
545
     * @param string $description
546
     */
547
    public function setDescription($description) {
548
549
        $description = strip_tags($description);
550
        $description = str_replace([PHP_EOL, '  '], ' ', $description);
551
        $description = rtrim($description, '!,.-:; ');
552
        if (mb_strlen($description) > $this->getDescLength()) {
553
            $description = mb_substr($description, 0, $this->getDescLength());
554
            $description = mb_substr($description, 0, mb_strrpos($description, ' '));
555
        }
556
557
        $this->description = $description;
558
    }
559
560
    /**
561
     * @return array|null|SBrands|SCategory|SProducts
562
     */
563
    public function getModel() {
564
565
        return $this->model;
566
    }
567
568
    /**
569
     * @param array|null|SBrands|SCategory|SProducts $model
570
     */
571
    public function setModel($model) {
572
573
        $this->model = $model;
574
    }
575
576
    /**
577
     * @return string
578
     */
579
    public function getID() {
580
581
        if (!$this->ID) {
582
            $this->setID($this->getModel()->getId());
583
        }
584
        return $this->ID;
585
    }
586
587
    /**
588
     * @param string $ID
589
     */
590
    public function setID($ID) {
591
592
        $this->ID = $ID;
593
    }
594
595
    /**
596
     * @return string
597
     */
598
    public function getMetaDescription() {
599
600
        return $this->metaDescription;
601
    }
602
603
    /**
604
     * @param string $metaDescription
605
     */
606
    public function setMetaDescription($metaDescription) {
607
608
        $this->metaDescription = $metaDescription;
609
    }
610
611
    /**
612
     * @return string
613
     */
614
    public function getMetaH1() {
615
616
        return $this->metaH1;
617
    }
618
619
    /**
620
     * @param string $metaH1
621
     */
622
    public function setMetaH1($metaH1) {
623
624
        $this->metaH1 = $metaH1;
625
    }
626
627
    /**
628
     * @return string
629
     */
630
    public function getMetaKeywords() {
631
632
        return $this->metaKeywords;
633
    }
634
635
    /**
636
     * @param string $metaKeywords
637
     */
638
    public function setMetaKeywords($metaKeywords) {
639
640
        $this->metaKeywords = $metaKeywords;
641
    }
642
643
    /**
644
     * @return string
645
     */
646
    public function getMetaTitle() {
647
648
        return $this->metaTitle;
649
    }
650
651
    /**
652
     * @param string $metaTitle
653
     */
654
    public function setMetaTitle($metaTitle) {
655
656
        $this->metaTitle = $metaTitle;
657
    }
658
659
    /**
660
     * @return string
661
     */
662
    public function getName() {
663
664
        if (!$this->name) {
665
            $this->setName($this->getModel()->getName());
666
        }
667
        return $this->name;
668
    }
669
670
    /**
671
     * @param string $name
672
     */
673
    public function setName($name) {
674
675
        $this->name = $name;
676
    }
677
678
    /**
679
     * @return string
680
     */
681
    public function getPageNumber() {
682
683
        return $this->getNumber() ? str_replace('%number%', $this->getNumber(), $this->pageNumber) : '';
684
    }
685
686
    /**
687
     * @param string $pageNumber
688
     */
689
    public function setPageNumber($pageNumber) {
690
691
        $this->pageNumber = $pageNumber;
692
    }
693
694
    /**
695
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be integer|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
696
     */
697
    public function getNumber() {
698
699
        if (!$this->number) {
700
            $this->setNumber(assetManager::create()->getData('page_number'));
701
        }
702
        return (int) $this->number > 1 ? (int) $this->number : '';
703
    }
704
705
    /**
706
     * @param string $number
707
     */
708
    public function setNumber($number) {
709
710
        $this->number = $number;
0 ignored issues
show
Documentation Bug introduced by
The property $number was declared of type integer, but $number is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
711
    }
712
713
    /**
714
     * @return string
715
     */
716
    public function getPrice() {
717
718
        return $this->price;
719
    }
720
721
    /**
722
     * @param string $price
723
     */
724
    public function setPrice($price) {
725
726
        $this->price = $price;
727
    }
728
729
    /**
730
     * @return array<String>
731
     */
732
    public function render() {
733
734
        $return = [];
735
        /** @var string $w wrapper */
736
        $w = $this->getWrapper();
737
        $vars = $this->getStorage()->getVars();
738
        if ($vars !== []) {
739
            foreach ($vars as $var) {
740
                $method = $this->getMatching($var) ?: $var;
741
                $method = "get$method";
742
743
                $replace = $this->$method();
744
                $search = $w . $var . $w;
745
746 View Code Duplication
                foreach ($this->getMetaArray() as $metaName) {
747
                    $get = "get$metaName";
748
                    $set = "set$metaName";
749
750
                    $return[$metaName] = str_replace($search, $replace, trim($this->$get()));
751
                    $this->$set($return[$metaName]);
752
                }
753
            }
754
        } else {
755 View Code Duplication
            foreach ($this->getMetaArray() as $metaName) {
756
                $get = "get$metaName";
757
                $set = "set$metaName";
758
759
                $return[$metaName] = trim($this->$get());
760
                $this->$set($return[$metaName]);
761
            }
762
763
        }
764
765
        return $return;
766
767
    }
768
769
    /**
770
     * @return string
771
     */
772
    public function getWrapper() {
773
774
        return $this->wrapper;
775
    }
776
777
    /**
778
     * @param string $wrapper
779
     */
780
    public function setWrapper($wrapper) {
781
782
        $this->wrapper = $wrapper;
783
    }
784
785
    /**
786
     * @param string $key
787
     * @return bool|array
788
     */
789
    public function getMatching($key) {
790
791
        return array_key_exists($key, $this->matching) ? $this->matching[$key] : false;
792
    }
793
794
    /**
795
     * @param string $key
796
     * @param string $value
797
     */
798
    public function setMatching($key, $value) {
799
800
        $this->matching[$key] = $value;
801
    }
802
803
    /**
804
     * @return array
805
     */
806
    public function getMetaArray() {
807
808
        return $this->metaArray;
809
    }
810
811
    /**
812
     * @param array $metaArray
813
     */
814
    public function setMetaArray($metaArray) {
815
816
        foreach ($metaArray as $item) {
817
            $this->metaArray[] = $item;
818
        }
819
820
        $this->metaArray = array_unique($this->metaArray);
821
    }
822
}