Completed
Push — master ( 05fe51...2a9bf3 )
by Arjay
01:59
created

BaseEngine::setTotalRecords()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
rs 9.4285
1
<?php
2
3
namespace Yajra\Datatables\Engines;
4
5
use Illuminate\Contracts\Logging\Log;
6
use Illuminate\Http\JsonResponse;
7
use Illuminate\Support\Str;
8
use Yajra\Datatables\Contracts\DataTableEngine;
9
use Yajra\Datatables\Exception;
10
use Yajra\Datatables\Helper;
11
use Yajra\Datatables\Processors\DataProcessor;
12
13
/**
14
 * Class BaseEngine.
15
 *
16
 * @package Yajra\Datatables\Engines
17
 * @author  Arjay Angeles <[email protected]>
18
 */
19
abstract class BaseEngine implements DataTableEngine
20
{
21
    /**
22
     * Datatables Request object.
23
     *
24
     * @var \Yajra\Datatables\Request
25
     */
26
    public $request;
27
28
    /**
29
     * @var \Illuminate\Contracts\Logging\Log
30
     */
31
    protected $logger;
32
33
    /**
34
     * Array of result columns/fields.
35
     *
36
     * @var array
37
     */
38
    protected $columns = [];
39
40
    /**
41
     * DT columns definitions container (add/edit/remove/filter/order/escape).
42
     *
43
     * @var array
44
     */
45
    protected $columnDef = [
46
        'index'  => false,
47
        'append' => [],
48
        'edit'   => [],
49
        'filter' => [],
50
        'order'  => [],
51
    ];
52
53
    /**
54
     * Extra/Added columns.
55
     *
56
     * @var array
57
     */
58
    protected $extraColumns = [];
59
60
    /**
61
     * Total records.
62
     *
63
     * @var int
64
     */
65
    protected $totalRecords = 0;
66
67
    /**
68
     * Total filtered records.
69
     *
70
     * @var int
71
     */
72
    protected $filteredRecords = 0;
73
74
    /**
75
     * Auto-filter flag.
76
     *
77
     * @var bool
78
     */
79
    protected $autoFilter = true;
80
81
    /**
82
     * Callback to override global search.
83
     *
84
     * @var callable
85
     */
86
    protected $filterCallback;
87
88
    /**
89
     * Parameters to passed on filterCallback.
90
     *
91
     * @var mixed
92
     */
93
    protected $filterCallbackParameters;
94
95
    /**
96
     * DT row templates container.
97
     *
98
     * @var array
99
     */
100
    protected $templates = [
101
        'DT_RowId'    => '',
102
        'DT_RowClass' => '',
103
        'DT_RowData'  => [],
104
        'DT_RowAttr'  => [],
105
    ];
106
107
    /**
108
     * Output transformer.
109
     *
110
     * @var \League\Fractal\TransformerAbstract
111
     */
112
    protected $transformer = null;
113
114
    /**
115
     * Database prefix.
116
     *
117
     * @var string
118
     */
119
    protected $prefix;
120
121
    /**
122
     * Database driver used.
123
     *
124
     * @var string
125
     */
126
    protected $database;
127
128
    /**
129
     * [internal] Track if any filter was applied for at least one column.
130
     *
131
     * @var boolean
132
     */
133
    protected $isFilterApplied = false;
134
135
    /**
136
     * Fractal serializer class.
137
     *
138
     * @var string|null
139
     */
140
    protected $serializer = null;
141
142
    /**
143
     * Custom ordering callback.
144
     *
145
     * @var callable
146
     */
147
    protected $orderCallback;
148
149
    /**
150
     * Skip paginate as needed.
151
     *
152
     * @var bool
153
     */
154
    protected $skipPaging = false;
155
156
    /**
157
     * Array of data to append on json response.
158
     *
159
     * @var array
160
     */
161
    protected $appends = [];
162
163
    /**
164
     * Flag for ordering NULLS LAST option.
165
     *
166
     * @var bool
167
     */
168
    protected $nullsLast = false;
169
170
    /**
171
     * Add column in collection.
172
     *
173
     * @param string          $name
174
     * @param string|callable $content
175
     * @param bool|int        $order
176
     * @return $this
177
     */
178
    public function addColumn($name, $content, $order = false)
179
    {
180
        $this->extraColumns[] = $name;
181
182
        $this->columnDef['append'][] = ['name' => $name, 'content' => $content, 'order' => $order];
183
184
        return $this;
185
    }
186
187
    /**
188
     * Add DT row index column on response.
189
     *
190
     * @return $this
191
     */
192
    public function addIndexColumn()
193
    {
194
        $this->columnDef['index'] = true;
195
196
        return $this;
197
    }
198
199
    /**
200
     * Edit column's content.
201
     *
202
     * @param string          $name
203
     * @param string|callable $content
204
     * @return $this
205
     */
206
    public function editColumn($name, $content)
207
    {
208
        $this->columnDef['edit'][] = ['name' => $name, 'content' => $content];
209
210
        return $this;
211
    }
212
213
    /**
214
     * Remove column from collection.
215
     *
216
     * @return $this
217
     */
218
    public function removeColumn()
219
    {
220
        $names                     = func_get_args();
221
        $this->columnDef['excess'] = array_merge($this->columnDef['excess'], $names);
222
223
        return $this;
224
    }
225
226
    /**
227
     * Declare columns to escape values.
228
     *
229
     * @param string|array $columns
230
     * @return $this
231
     */
232
    public function escapeColumns($columns = '*')
233
    {
234
        $this->columnDef['escape'] = $columns;
235
236
        return $this;
237
    }
238
239
    /**
240
     * Set columns that should not be escaped.
241
     *
242
     * @param array $columns
243
     * @return $this
244
     */
245
    public function rawColumns(array $columns)
246
    {
247
        $this->columnDef['raw'] = $columns;
248
249
        return $this;
250
    }
251
252
    /**
253
     * Sets DT_RowClass template.
254
     * result: <tr class="output_from_your_template">.
255
     *
256
     * @param string|callable $content
257
     * @return $this
258
     */
259
    public function setRowClass($content)
260
    {
261
        $this->templates['DT_RowClass'] = $content;
262
263
        return $this;
264
    }
265
266
    /**
267
     * Sets DT_RowId template.
268
     * result: <tr id="output_from_your_template">.
269
     *
270
     * @param string|callable $content
271
     * @return $this
272
     */
273
    public function setRowId($content)
274
    {
275
        $this->templates['DT_RowId'] = $content;
276
277
        return $this;
278
    }
279
280
    /**
281
     * Set DT_RowData templates.
282
     *
283
     * @param array $data
284
     * @return $this
285
     */
286
    public function setRowData(array $data)
287
    {
288
        $this->templates['DT_RowData'] = $data;
289
290
        return $this;
291
    }
292
293
    /**
294
     * Add DT_RowData template.
295
     *
296
     * @param string          $key
297
     * @param string|callable $value
298
     * @return $this
299
     */
300
    public function addRowData($key, $value)
301
    {
302
        $this->templates['DT_RowData'][$key] = $value;
303
304
        return $this;
305
    }
306
307
    /**
308
     * Set DT_RowAttr templates.
309
     * result: <tr attr1="attr1" attr2="attr2">.
310
     *
311
     * @param array $data
312
     * @return $this
313
     */
314
    public function setRowAttr(array $data)
315
    {
316
        $this->templates['DT_RowAttr'] = $data;
317
318
        return $this;
319
    }
320
321
    /**
322
     * Add DT_RowAttr template.
323
     *
324
     * @param string          $key
325
     * @param string|callable $value
326
     * @return $this
327
     */
328
    public function addRowAttr($key, $value)
329
    {
330
        $this->templates['DT_RowAttr'][$key] = $value;
331
332
        return $this;
333
    }
334
335
    /**
336
     * Add custom filter handler for the give column.
337
     *
338
     * @param string   $column
339
     * @param callable $callback
340
     * @return $this
341
     */
342
    public function filterColumn($column, callable $callback)
343
    {
344
        $this->columnDef['filter'][$column] = ['method' => $callback];
345
346
        return $this;
347
    }
348
349
    /**
350
     * Order each given columns versus the given custom sql.
351
     *
352
     * @param array  $columns
353
     * @param string $sql
354
     * @param array  $bindings
355
     * @return $this
356
     */
357
    public function orderColumns(array $columns, $sql, $bindings = [])
358
    {
359
        foreach ($columns as $column) {
360
            $this->orderColumn($column, str_replace(':column', $column, $sql), $bindings);
361
        }
362
363
        return $this;
364
    }
365
366
    /**
367
     * Override default column ordering.
368
     *
369
     * @param string $column
370
     * @param string $sql
371
     * @param array  $bindings
372
     * @return $this
373
     * @internal string $1 Special variable that returns the requested order direction of the column.
374
     */
375
    public function orderColumn($column, $sql, $bindings = [])
376
    {
377
        $this->columnDef['order'][$column] = compact('sql', 'bindings');
378
379
        return $this;
380
    }
381
382
    /**
383
     * Set data output transformer.
384
     *
385
     * @param \League\Fractal\TransformerAbstract $transformer
386
     * @return $this
387
     */
388
    public function setTransformer($transformer)
389
    {
390
        $this->transformer = $transformer;
391
392
        return $this;
393
    }
394
395
    /**
396
     * Set fractal serializer class.
397
     *
398
     * @param string $serializer
399
     * @return $this
400
     */
401
    public function setSerializer($serializer)
402
    {
403
        $this->serializer = $serializer;
404
405
        return $this;
406
    }
407
408
    /**
409
     * Check if config uses case insensitive search.
410
     *
411
     * @return bool
412
     */
413
    public function isCaseInsensitive()
414
    {
415
        return config('datatables.search.case_insensitive', false);
416
    }
417
418
    /**
419
     * Append data on json response.
420
     *
421
     * @param mixed $key
422
     * @param mixed $value
423
     * @return $this
424
     */
425
    public function with($key, $value = '')
426
    {
427
        if (is_array($key)) {
428
            $this->appends = $key;
429
        } elseif (is_callable($value)) {
430
            $this->appends[$key] = value($value);
431
        } else {
432
            $this->appends[$key] = value($value);
433
        }
434
435
        return $this;
436
    }
437
438
    /**
439
     * Override default ordering method with a closure callback.
440
     *
441
     * @param callable $closure
442
     * @return $this
443
     */
444
    public function order(callable $closure)
445
    {
446
        $this->orderCallback = $closure;
447
448
        return $this;
449
    }
450
451
    /**
452
     * Update list of columns that is not allowed for search/sort.
453
     *
454
     * @param  array $blacklist
455
     * @return $this
456
     */
457
    public function blacklist(array $blacklist)
458
    {
459
        $this->columnDef['blacklist'] = $blacklist;
460
461
        return $this;
462
    }
463
464
    /**
465
     * Update list of columns that is allowed for search/sort.
466
     *
467
     * @param  string|array $whitelist
468
     * @return $this
469
     */
470
    public function whitelist($whitelist = '*')
471
    {
472
        $this->columnDef['whitelist'] = $whitelist;
473
474
        return $this;
475
    }
476
477
    /**
478
     * Set smart search config at runtime.
479
     *
480
     * @param bool $bool
481
     * @return $this
482
     */
483
    public function smart($bool = true)
484
    {
485
        config(['datatables.search.smart' => $bool]);
486
487
        return $this;
488
    }
489
490
    /**
491
     * Set total records manually.
492
     *
493
     * @param int $total
494
     * @return $this
495
     */
496
    public function setTotalRecords($total)
497
    {
498
        $this->totalRecords = $total;
499
500
        return $this;
501
    }
502
503
    /**
504
     * Skip pagination as needed.
505
     *
506
     * @return $this
507
     */
508
    public function skipPaging()
509
    {
510
        $this->skipPaging = true;
511
512
        return $this;
513
    }
514
515
    /**
516
     * Check if the current sql language is based on oracle syntax.
517
     *
518
     * @return bool
519
     */
520
    public function isOracleSql()
521
    {
522
        return in_array($this->database, ['oracle', 'oci8']);
523
    }
524
525
    /**
526
     * Set datatables to do ordering with NULLS LAST option.
527
     *
528
     * @return $this
529
     */
530
    public function orderByNullsLast()
531
    {
532
        $this->nullsLast = true;
533
534
        return $this;
535
    }
536
537
    /**
538
     * Push a new column name to blacklist.
539
     *
540
     * @param string $column
541
     * @return $this
542
     */
543
    public function pushToBlacklist($column)
544
    {
545
        if (!$this->isBlacklisted($column)) {
546
            $this->columnDef['blacklist'][] = $column;
547
        }
548
549
        return $this;
550
    }
551
552
    /**
553
     * Check if column is blacklisted.
554
     *
555
     * @param string $column
556
     * @return bool
557
     */
558
    protected function isBlacklisted($column)
559
    {
560
        $colDef = $this->getColumnsDefinition();
561
562
        if (in_array($column, $colDef['blacklist'])) {
563
            return true;
564
        }
565
566
        if ($colDef['whitelist'] === '*' || in_array($column, $colDef['whitelist'])) {
567
            return false;
568
        }
569
570
        return true;
571
    }
572
573
    /**
574
     * Perform necessary filters.
575
     *
576
     * @return void
577
     */
578
    protected function filterRecords()
579
    {
580
        if ($this->autoFilter && $this->request->isSearchable()) {
581
            $this->filtering();
582
        }
583
584
        if (is_callable($this->filterCallback)) {
585
            call_user_func($this->filterCallback, $this->filterCallbackParameters);
586
        }
587
588
        $this->columnSearch();
589
        $this->filteredRecords = $this->isFilterApplied ? $this->count() : $this->totalRecords;
590
    }
591
592
    /**
593
     * Apply pagination.
594
     *
595
     * @return void
596
     */
597
    protected function paginate()
598
    {
599
        if ($this->request->isPaginationable() && !$this->skipPaging) {
600
            $this->paging();
601
        }
602
    }
603
604
    /**
605
     * Transform output.
606
     *
607
     * @param mixed $output
608
     * @return array
609
     */
610
    protected function transform($output)
611
    {
612
        if (!isset($this->transformer)) {
613
            return Helper::transform($output);
614
        }
615
616
        $fractal = app('datatables.fractal');
617
618
        if ($this->serializer) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->serializer of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
619
            $fractal->setSerializer($this->createSerializer());
620
        }
621
622
        //Get transformer reflection
623
        //Firs method parameter should be data/object to transform
624
        $reflection = new \ReflectionMethod($this->transformer, 'transform');
625
        $parameter  = $reflection->getParameters()[0];
626
627
        //If parameter is class assuming it requires object
628
        //Else just pass array by default
629
        if ($parameter->getClass()) {
630
            $resource = new Collection($output, $this->createTransformer());
631
        } else {
632
            $resource = new Collection(
633
                $output,
634
                $this->createTransformer()
635
            );
636
        }
637
638
        $collection = $fractal->createData($resource)->toArray();
639
640
        return $collection['data'];
641
    }
642
643
    /**
644
     * Get or create transformer serializer instance.
645
     *
646
     * @return \League\Fractal\Serializer\SerializerAbstract
647
     */
648
    protected function createSerializer()
649
    {
650
        if ($this->serializer instanceof \League\Fractal\Serializer\SerializerAbstract) {
0 ignored issues
show
Bug introduced by
The class League\Fractal\Serializer\SerializerAbstract does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
651
            return $this->serializer;
652
        }
653
654
        return new $this->serializer();
655
    }
656
657
    /**
658
     * Get or create transformer instance.
659
     *
660
     * @return \League\Fractal\TransformerAbstract
661
     */
662
    protected function createTransformer()
663
    {
664
        if ($this->transformer instanceof \League\Fractal\TransformerAbstract) {
0 ignored issues
show
Bug introduced by
The class League\Fractal\TransformerAbstract does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
665
            return $this->transformer;
666
        }
667
668
        return new $this->transformer();
669
    }
670
671
    /**
672
     * Get processed data.
673
     *
674
     * @param bool|false $object
675
     * @return array
676
     */
677
    protected function getProcessedData($object = false)
678
    {
679
        $processor = new DataProcessor(
680
            $this->results(),
681
            $this->getColumnsDefinition(),
682
            $this->templates,
683
            $this->request->input('start')
684
        );
685
686
        return $processor->process($object);
687
    }
688
689
    /**
690
     * Get columns definition.
691
     *
692
     * @return array
693
     */
694
    protected function getColumnsDefinition()
695
    {
696
        $config  = config('datatables.columns');
697
        $allowed = ['excess', 'escape', 'raw', 'blacklist', 'whitelist'];
698
699
        return array_merge(array_only($config, $allowed), $this->columnDef);
700
    }
701
702
    /**
703
     * Render json response.
704
     *
705
     * @param array $data
706
     * @return \Illuminate\Http\JsonResponse
707
     */
708
    protected function render(array $data)
709
    {
710
        $output = array_merge([
711
            'draw'            => (int) $this->request->input('draw'),
712
            'recordsTotal'    => $this->totalRecords,
713
            'recordsFiltered' => $this->filteredRecords,
714
            'data'            => $data,
715
        ], $this->appends);
716
717
        if ($this->isDebugging()) {
718
            $output = $this->showDebugger($output);
719
        }
720
721
        return new JsonResponse(
722
            $output,
723
            200,
724
            config('datatables.json.header', []),
725
            config('datatables.json.options', 0)
726
        );
727
    }
728
729
    /**
730
     * Check if app is in debug mode.
731
     *
732
     * @return bool
733
     */
734
    public function isDebugging()
735
    {
736
        return config('app.debug', false);
737
    }
738
739
    /**
740
     * Append debug parameters on output.
741
     *
742
     * @param  array $output
743
     * @return array
744
     */
745
    abstract protected function showDebugger(array $output);
746
747
    /**
748
     * Return an error json response.
749
     *
750
     * @param \Exception $exception
751
     * @return \Illuminate\Http\JsonResponse
752
     * @throws \Yajra\Datatables\Exception
753
     */
754
    protected function errorResponse(\Exception $exception)
755
    {
756
        $error = config('datatables.error');
757
        if ($error === 'throw') {
758
            throw new Exception($exception->getMessage(), $code = 0, $exception);
759
        }
760
761
        $this->getLogger()->error($exception);
762
763
        return new JsonResponse([
764
            'draw'            => (int) $this->request->input('draw'),
765
            'recordsTotal'    => (int) $this->totalRecords,
766
            'recordsFiltered' => 0,
767
            'data'            => [],
768
            'error'           => $error ? __($error) : "Exception Message:\n\n" . $exception->getMessage(),
769
        ]);
770
    }
771
772
    /**
773
     * Get monolog/logger instance.
774
     *
775
     * @return \Illuminate\Contracts\Logging\Log
776
     */
777
    public function getLogger()
778
    {
779
        $this->logger = $this->logger ?: resolve(Log::class);
780
781
        return $this->logger;
782
    }
783
784
    /**
785
     * Set monolog/logger instance.
786
     *
787
     * @param \Illuminate\Contracts\Logging\Log $logger
788
     * @return $this
789
     */
790
    public function setLogger(Log $logger)
791
    {
792
        $this->logger = $logger;
793
794
        return $this;
795
    }
796
797
    /**
798
     * Setup search keyword.
799
     *
800
     * @param  string $value
801
     * @return string
802
     */
803
    protected function setupKeyword($value)
804
    {
805
        if ($this->isSmartSearch()) {
806
            $keyword = '%' . $value . '%';
807
            if ($this->isWildcard()) {
808
                $keyword = $this->wildcardLikeString($value);
809
            }
810
            // remove escaping slash added on js script request
811
            $keyword = str_replace('\\', '%', $keyword);
812
813
            return $keyword;
814
        }
815
816
        return $value;
817
    }
818
819
    /**
820
     * Check if config uses smart search.
821
     *
822
     * @return bool
823
     */
824
    public function isSmartSearch()
825
    {
826
        return config('datatables.search.smart', true);
827
    }
828
829
    /**
830
     * Check if config uses wild card search.
831
     *
832
     * @return bool
833
     */
834
    public function isWildcard()
835
    {
836
        return config('datatables.search.use_wildcards', false);
837
    }
838
839
    /**
840
     * Adds % wildcards to the given string.
841
     *
842
     * @param string $str
843
     * @param bool   $lowercase
844
     * @return string
845
     */
846
    protected function wildcardLikeString($str, $lowercase = true)
847
    {
848
        $wild  = '%';
849
        $chars = preg_split('//u', $str, -1, PREG_SPLIT_NO_EMPTY);
850
851
        if (count($chars) > 0) {
852
            foreach ($chars as $char) {
853
                $wild .= $char . '%';
854
            }
855
        }
856
857
        if ($lowercase) {
858
            $wild = Str::lower($wild);
859
        }
860
861
        return $wild;
862
    }
863
864
    /**
865
     * Update flags to disable global search.
866
     *
867
     * @param  callable $callback
868
     * @param  mixed    $parameters
869
     * @param  bool     $autoFilter
870
     */
871
    protected function overrideGlobalSearch(callable $callback, $parameters, $autoFilter = false)
872
    {
873
        $this->autoFilter               = $autoFilter;
874
        $this->isFilterApplied          = true;
875
        $this->filterCallback           = $callback;
876
        $this->filterCallbackParameters = $parameters;
877
    }
878
879
    /**
880
     * Get column name to be use for filtering and sorting.
881
     *
882
     * @param integer $index
883
     * @param bool    $wantsAlias
884
     * @return string
885
     */
886
    protected function getColumnName($index, $wantsAlias = false)
887
    {
888
        $column = $this->request->columnName($index);
889
890
        // DataTables is using make(false)
891
        if (is_numeric($column)) {
892
            $column = $this->getColumnNameByIndex($index);
893
        }
894
895
        if (Str::contains(Str::upper($column), ' AS ')) {
896
            $column = $this->extractColumnName($column, $wantsAlias);
897
        }
898
899
        return $column;
900
    }
901
902
    /**
903
     * Get column name by order column index.
904
     *
905
     * @param int $index
906
     * @return string
907
     */
908
    protected function getColumnNameByIndex($index)
909
    {
910
        $name = (isset($this->columns[$index]) && $this->columns[$index] != '*') ? $this->columns[$index] : $this->getPrimaryKeyName();
911
912
        return in_array($name, $this->extraColumns, true) ? $this->getPrimaryKeyName() : $name;
913
    }
914
915
    /**
916
     * If column name could not be resolved then use primary key.
917
     *
918
     * @return string
919
     */
920
    protected function getPrimaryKeyName()
921
    {
922
        return 'id';
923
    }
924
925
    /**
926
     * Get column name from string.
927
     *
928
     * @param string $str
929
     * @param bool   $wantsAlias
930
     * @return string
931
     */
932
    protected function extractColumnName($str, $wantsAlias)
933
    {
934
        $matches = explode(' as ', Str::lower($str));
935
936
        if (!empty($matches)) {
937
            if ($wantsAlias) {
938
                return array_pop($matches);
939
            } else {
940
                return array_shift($matches);
941
            }
942
        } elseif (strpos($str, '.')) {
943
            $array = explode('.', $str);
944
945
            return array_pop($array);
946
        }
947
948
        return $str;
949
    }
950
}
951