Completed
Pull Request — develop (#179)
by Wachter
13:33
created

ArticleController::persistDocument()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 25
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2.0116

Importance

Changes 0
Metric Value
dl 0
loc 25
ccs 6
cts 7
cp 0.8571
rs 8.8571
c 0
b 0
f 0
cc 2
eloc 13
nc 2
nop 3
crap 2.0116
1
<?php
2
3
/*
4
 * This file is part of Sulu.
5
 *
6
 * (c) MASSIVE ART WebServices GmbH
7
 *
8
 * This source file is subject to the MIT license that is bundled
9
 * with this source code in the file LICENSE.
10
 */
11
12
namespace Sulu\Bundle\ArticleBundle\Controller;
13
14
use FOS\RestBundle\Controller\Annotations\Post;
15
use FOS\RestBundle\Routing\ClassResourceInterface;
16
use JMS\Serializer\SerializationContext;
17
use ONGR\ElasticsearchBundle\Service\Manager;
18
use ONGR\ElasticsearchDSL\Query\Compound\BoolQuery;
19
use ONGR\ElasticsearchDSL\Query\FullText\MatchQuery;
20
use ONGR\ElasticsearchDSL\Query\FullText\MultiMatchQuery;
21
use ONGR\ElasticsearchDSL\Query\MatchAllQuery;
22
use ONGR\ElasticsearchDSL\Query\TermLevel\IdsQuery;
23
use ONGR\ElasticsearchDSL\Query\TermLevel\TermQuery;
24
use ONGR\ElasticsearchDSL\Sort\FieldSort;
25
use Sulu\Bundle\ArticleBundle\Admin\ArticleAdmin;
26
use Sulu\Bundle\ArticleBundle\Document\ArticleDocument;
27
use Sulu\Bundle\ArticleBundle\Document\Form\ArticleDocumentType;
28
use Sulu\Bundle\ArticleBundle\Metadata\ArticleViewDocumentIdTrait;
29
use Sulu\Component\Content\Form\Exception\InvalidFormException;
30
use Sulu\Component\Content\Mapper\ContentMapperInterface;
31
use Sulu\Component\DocumentManager\DocumentManagerInterface;
32
use Sulu\Component\Rest\Exception\MissingParameterException;
33
use Sulu\Component\Rest\Exception\RestException;
34
use Sulu\Component\Rest\ListBuilder\FieldDescriptor;
35
use Sulu\Component\Rest\ListBuilder\ListRepresentation;
36
use Sulu\Component\Rest\RequestParametersTrait;
37
use Sulu\Component\Rest\RestController;
38
use Sulu\Component\Security\Authorization\PermissionTypes;
39
use Sulu\Component\Security\Authorization\SecurityCondition;
40
use Sulu\Component\Security\SecuredControllerInterface;
41
use Symfony\Component\HttpFoundation\Request;
42
use Symfony\Component\HttpFoundation\Response;
43
44
/**
45
 * Provides API for articles.
46
 */
47
class ArticleController extends RestController implements ClassResourceInterface, SecuredControllerInterface
48
{
49
    const DOCUMENT_TYPE = 'article';
50
51
    use RequestParametersTrait;
52
    use ArticleViewDocumentIdTrait;
53
54
    /**
55
     * Create field-descriptor array.
56
     *
57
     * @return FieldDescriptor[]
58
     */
59
    private function getFieldDescriptors()
60
    {
61
        return [
62
            'uuid' => new FieldDescriptor('uuid', 'public.id', true),
63
            'typeTranslation' => new FieldDescriptor(
64
                'typeTranslation',
65
                'sulu_article.list.type',
66
                !$this->getParameter('sulu_article.display_tab_all'),
67
                false
68
            ),
69
            'title' => new FieldDescriptor('title', 'public.title', false, true),
70
            'creatorFullName' => new FieldDescriptor('creatorFullName', 'sulu_article.list.creator', true, false),
71
            'changerFullName' => new FieldDescriptor('changerFullName', 'sulu_article.list.changer', false, false),
72
            'authorFullName' => new FieldDescriptor('authorFullName', 'sulu_article.author', false, false),
73
            'created' => new FieldDescriptor('created', 'public.created', true, false, 'datetime'),
74
            'changed' => new FieldDescriptor('changed', 'public.changed', false, false, 'datetime'),
75
            'authored' => new FieldDescriptor('authored', 'sulu_article.authored', false, false, 'date'),
76
        ];
77
    }
78
79
    /**
80
     * Returns fields.
81
     *
82
     * @return Response
83
     */
84
    public function cgetFieldsAction()
85
    {
86
        $fieldDescriptors = $this->getFieldDescriptors();
87
88
        return $this->handleView($this->view(array_values($fieldDescriptors)));
0 ignored issues
show
Documentation introduced by
$this->view(array_values($fieldDescriptors)) is of type this<Sulu\Bundle\Article...ller\ArticleController>, but the function expects a object<FOS\RestBundle\View\View>.

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...
89
    }
90
91
    /**
92
     * Returns list of articles.
93
     *
94
     * @param Request $request
95
     *
96
     * @return Response
97
     */
98 12
    public function cgetAction(Request $request)
99
    {
100 12
        $locale = $this->getRequestParameter($request, 'locale', true);
101
102 12
        $restHelper = $this->get('sulu_core.list_rest_helper');
103
104
        /** @var Manager $manager */
105 12
        $manager = $this->get('es.manager.default');
106 12
        $repository = $manager->getRepository($this->get('sulu_article.view_document.factory')->getClass('article'));
107 12
        $search = $repository->createSearch();
108
109 12
        $limit = (int) $restHelper->getLimit();
110 12
        $page = (int) $restHelper->getPage();
111
112 12
        if (null !== $locale) {
113 12
            $search->addQuery(new TermQuery('locale', $locale));
114
        }
115
116 12
        if (count($ids = array_filter(explode(',', $request->get('ids', ''))))) {
117 1
            $search->addQuery(new IdsQuery($this->getViewDocumentIds($ids, $locale)));
118 1
            $limit = count($ids);
119
        }
120
121 12
        if (!empty($searchPattern = $restHelper->getSearchPattern())
122 12
            && 0 < count($searchFields = $restHelper->getSearchFields())
123
        ) {
124 2
            $search->addQuery(new MultiMatchQuery($searchFields, $searchPattern));
125
        }
126
127 12
        if (null !== ($type = $request->get('type'))) {
128 10
            $search->addQuery(new TermQuery('type', $type));
129
        }
130
131 12
        if (null !== ($contactId = $request->get('contactId'))) {
132 1
            $boolQuery = new BoolQuery();
133 1
            $boolQuery->add(new MatchQuery('changer_contact_id', $contactId), BoolQuery::SHOULD);
134 1
            $boolQuery->add(new MatchQuery('creator_contact_id', $contactId), BoolQuery::SHOULD);
135 1
            $boolQuery->add(new MatchQuery('author_id', $contactId), BoolQuery::SHOULD);
136 1
            $search->addQuery($boolQuery);
137
        }
138
139 12
        if (null !== ($categoryId = $request->get('categoryId'))) {
140 1
            $search->addQuery(new TermQuery('excerpt.categories.id', $categoryId), BoolQuery::MUST);
141
        }
142
143 12
        if (null === $search->getQueries()) {
144
            $search->addQuery(new MatchAllQuery());
145
        }
146
147 12
        $count = $repository->count($search);
148
149 12
        if (null !== $restHelper->getSortColumn()) {
150 1
            $search->addSort(
151 1
                new FieldSort($this->uncamelize($restHelper->getSortColumn()), $restHelper->getSortOrder())
152
            );
153
        }
154
155 12
        $search->setSize($limit);
156 12
        $search->setFrom(($page - 1) * $limit);
157
158 12
        $result = [];
159 12
        foreach ($repository->findDocuments($search) as $document) {
160 10
            if (false !== ($index = array_search($document->getUuid(), $ids))) {
161 1
                $result[$index] = $document;
162
            } else {
163 12
                $result[] = $document;
164
            }
165
        }
166
167 12
        if (count($ids)) {
168 1
            ksort($result);
169 1
            $result = array_values($result);
170
        }
171
172 12
        return $this->handleView(
173 12
            $this->view(
0 ignored issues
show
Documentation introduced by
$this->view(new \Sulu\Co...$page, $limit, $count)) is of type this<Sulu\Bundle\Article...ller\ArticleController>, but the function expects a object<FOS\RestBundle\View\View>.

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...
174 12
                new ListRepresentation(
175
                    $result,
176 12
                    'articles',
177 12
                    'get_articles',
178 12
                    $request->query->all(),
179
                    $page,
180
                    $limit,
181 12
                    $count
0 ignored issues
show
Bug introduced by
It seems like $count defined by $repository->count($search) on line 147 can also be of type array; however, Sulu\Component\Rest\List...entation::__construct() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
182
                )
183
            )
184
        );
185
    }
186
187
    /**
188
     * Returns single article.
189
     *
190
     * @param string  $uuid
191
     * @param Request $request
192
     *
193
     * @return Response
194
     */
195 9
    public function getAction($uuid, Request $request)
196
    {
197 9
        $locale = $this->getRequestParameter($request, 'locale', true);
198 9
        $document = $this->getDocumentManager()->find(
199
            $uuid,
200
            $locale,
201
            [
202 9
                'load_ghost_content' => true,
203
                'load_shadow_content' => false,
204
            ]
205
        );
206
207 9
        return $this->handleView(
208 9
            $this->view($document)->setSerializationContext(
0 ignored issues
show
Bug introduced by
The method setSerializationContext() does not seem to exist on object<Sulu\Bundle\Artic...ller\ArticleController>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
209 9
                SerializationContext::create()
210 9
                    ->setSerializeNull(true)
211 9
                    ->setGroups(['defaultPage', 'defaultArticle', 'smallArticlePage'])
212
            )
213
        );
214
    }
215
216
    /**
217
     * Create article.
218
     *
219
     * @param Request $request
220
     *
221
     * @return Response
222
     */
223 48 View Code Duplication
    public function postAction(Request $request)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
224
    {
225 48
        $action = $request->get('action');
226 48
        $document = $this->getDocumentManager()->create(self::DOCUMENT_TYPE);
227 48
        $locale = $this->getRequestParameter($request, 'locale', true);
228 48
        $data = $request->request->all();
229
230 48
        $this->persistDocument($data, $document, $locale);
231 48
        $this->handleActionParameter($action, $document, $locale);
232 48
        $this->getDocumentManager()->flush();
233
234 48
        return $this->handleView(
235 48
            $this->view($document)->setSerializationContext(
0 ignored issues
show
Bug introduced by
The method setSerializationContext() does not seem to exist on object<Sulu\Bundle\Artic...ller\ArticleController>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
236 48
                SerializationContext::create()
237 48
                    ->setSerializeNull(true)
238 48
                    ->setGroups(['defaultPage', 'defaultArticle', 'smallArticlePage'])
239
            )
240
        );
241
    }
242
243
    /**
244
     * Update articles.
245
     *
246
     * @param Request $request
247
     * @param string  $uuid
248
     *
249
     * @return Response
250
     */
251 9 View Code Duplication
    public function putAction(Request $request, $uuid)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
252
    {
253 9
        $locale = $this->getRequestParameter($request, 'locale', true);
254 9
        $action = $request->get('action');
255 9
        $data = $request->request->all();
256
257 9
        $document = $this->getDocumentManager()->find(
258
            $uuid,
259
            $locale,
260
            [
261 9
                'load_ghost_content' => false,
262
                'load_shadow_content' => false,
263
            ]
264
        );
265
266 9
        $this->get('sulu_hash.request_hash_checker')->checkHash($request, $document, $document->getUuid());
267
268 9
        $this->persistDocument($data, $document, $locale);
269 9
        $this->handleActionParameter($action, $document, $locale);
270 9
        $this->getDocumentManager()->flush();
271
272 9
        return $this->handleView(
273 9
            $this->view($document)->setSerializationContext(
0 ignored issues
show
Bug introduced by
The method setSerializationContext() does not seem to exist on object<Sulu\Bundle\Artic...ller\ArticleController>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
274 9
                SerializationContext::create()
275 9
                    ->setSerializeNull(true)
276 9
                    ->setGroups(['defaultPage', 'defaultArticle', 'smallArticlePage'])
277
            )
278
        );
279
    }
280
281
    /**
282
     * Deletes multiple documents.
283
     *
284
     * @param Request $request
285
     *
286
     * @return Response
287
     */
288 1
    public function cdeleteAction(Request $request)
289
    {
290 1
        $ids = array_filter(explode(',', $request->get('ids', '')));
291
292 1
        $documentManager = $this->getDocumentManager();
293 1
        foreach ($ids as $id) {
294 1
            $document = $documentManager->find($id);
295 1
            $documentManager->remove($document);
296 1
            $documentManager->flush();
297
        }
298
299 1
        return $this->handleView($this->view(null));
0 ignored issues
show
Documentation introduced by
$this->view(null) is of type this<Sulu\Bundle\Article...ller\ArticleController>, but the function expects a object<FOS\RestBundle\View\View>.

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...
300
    }
301
302
    /**
303
     * Deletes multiple documents.
304
     *
305
     * @param string $id
306
     *
307
     * @return Response
308
     */
309 1
    public function deleteAction($id)
310
    {
311 1
        $documentManager = $this->getDocumentManager();
312 1
        $document = $documentManager->find($id);
313 1
        $documentManager->remove($document);
314 1
        $documentManager->flush();
315
316 1
        return $this->handleView($this->view(null));
0 ignored issues
show
Documentation introduced by
$this->view(null) is of type this<Sulu\Bundle\Article...ller\ArticleController>, but the function expects a object<FOS\RestBundle\View\View>.

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...
317
    }
318
319
    /**
320
     * Trigger a action for given article specified over get-action parameter.
321
     *
322
     * @Post("/articles/{uuid}")
323
     *
324
     * @param string  $uuid
325
     * @param Request $request
326
     *
327
     * @return Response
328
     */
329 1
    public function postTriggerAction($uuid, Request $request)
330
    {
331
        // extract parameter
332 1
        $action = $this->getRequestParameter($request, 'action', true);
333 1
        $locale = $this->getRequestParameter($request, 'locale', true);
334
335
        // prepare vars
336 1
        $view = null;
337 1
        $data = null;
338 1
        $userId = $this->getUser()->getId();
339
340
        try {
341
            switch ($action) {
342 1
                case 'unpublish':
343
                    $document = $this->getDocumentManager()->find($uuid, $locale);
344
                    $this->getDocumentManager()->unpublish($document, $locale);
345
                    $this->getDocumentManager()->flush();
346
347
                    $data = $this->getDocumentManager()->find($uuid, $locale);
348
                    break;
349 1
                case 'remove-draft':
350
                    $data = $this->getDocumentManager()->find($uuid, $locale);
351
                    $this->getDocumentManager()->removeDraft($data, $locale);
352
                    $this->getDocumentManager()->flush();
353
                    break;
354 1
                case 'copy-locale':
355 1
                    $destLocales = $this->getRequestParameter($request, 'dest', true);
356 1
                    $destLocales = explode(',', $destLocales);
357
358 1
                    $securityChecker = $this->get('sulu_security.security_checker');
359 1
                    foreach ($destLocales as $destLocale) {
360 1
                        $securityChecker->checkPermission(
361 1
                            new SecurityCondition($this->getSecurityContext(), $destLocale),
362 1
                            PermissionTypes::EDIT
363
                        );
364
                    }
365
366 1
                    $this->getMapper()->copyLanguage($uuid, $userId, null, $locale, $destLocales);
367
368 1
                    $data = $this->getDocumentManager()->find($uuid, $locale);
369 1
                    break;
370
                case 'copy':
371
                    /** @var ArticleDocument $document */
372
                    $document = $this->getDocumentManager()->find($uuid, $locale);
373
                    $copiedPath = $this->getDocumentManager()->copy($document, dirname($document->getPath()));
374
                    $this->getDocumentManager()->flush();
375
376
                    $data = $this->getDocumentManager()->find($copiedPath, $locale);
377
                    break;
378
                case 'order':
379
                    $this->orderPages($this->getRequestParameter($request, 'pages', true), $locale);
0 ignored issues
show
Documentation introduced by
$this->getRequestParamet...request, 'pages', true) is of type string, but the function expects a array.

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...
380
                    $this->getDocumentManager()->flush();
381
                    $this->getDocumentManager()->clear();
382
383 1
                    $data = $this->getDocumentManager()->find($uuid, $locale);
384 1
                    break;
385 1
                default:
386 1
                    throw new RestException('Unrecognized action: ' . $action);
387 1
            }
388
389
            // prepare view
390
            $view = $this->view($data);
391
            $view->setSerializationContext(
0 ignored issues
show
Bug introduced by
The method setSerializationContext() does not seem to exist on object<Sulu\Bundle\Artic...ller\ArticleController>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
392
                SerializationContext::create()
393 1
                    ->setSerializeNull(true)
394
                    ->setGroups(['defaultPage', 'defaultArticle', 'smallArticlePage'])
395
            );
396
        } catch (RestException $exc) {
397
            $view = $this->view($exc->toArray(), 400);
398
        }
399 49
400
        return $this->handleView($view);
0 ignored issues
show
Documentation introduced by
$view is of type this<Sulu\Bundle\Article...ller\ArticleController>, but the function expects a object<FOS\RestBundle\View\View>.

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...
401 49
    }
402
403
    private function orderPages(array $pages, $locale)
404
    {
405
        $documentManager = $this->getDocumentManager();
406
407
        $tmp = [];
408
        for ($i = 0; $i < count($pages); ++$i) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
409
            $tmp[] = $document = $documentManager->find($pages[$i], $locale);
410
            $documentManager->reorder($document, null);
411
        }
412
    }
413
414 48
    /**
415
     * {@inheritdoc}
416 48
     */
417 48
    public function getSecurityContext()
418
    {
419
        return ArticleAdmin::SECURITY_CONTEXT;
420
    }
421 48
422
    /**
423
     * Persists the document using the given information.
424 48
     *
425
     * @param array  $data
426 48
     * @param object $document
427
     * @param string $locale
428
     *
429
     * @throws InvalidFormException
430 48
     * @throws MissingParameterException
431
     */
432
    private function persistDocument($data, $document, $locale)
433
    {
434 48
        $form = $this->createForm(
435
            ArticleDocumentType::class,
436
            $document,
437
            [
438 48
                // disable csrf protection, since we can't produce a token, because the form is cached on the client
439
                'csrf_protection' => false,
440
            ]
441
        );
442
        $form->submit($data, false);
443
444
        if (!$form->isValid()) {
445 48
            throw new InvalidFormException($form);
446
        }
447 48
448
        $this->getDocumentManager()->persist(
449
            $document,
450
            $locale,
451
            [
452
                'user' => $this->getUser()->getId(),
453 1
                'clear_missing_content' => false,
454
            ]
455 1
        );
456
    }
457
458
    /**
459
     * Returns document-manager.
460
     *
461
     * @return DocumentManagerInterface
462
     */
463
    protected function getDocumentManager()
464
    {
465 48
        return $this->get('sulu_document_manager.document_manager');
466
    }
467
468 48
    /**
469 15
     * @return ContentMapperInterface
470 15
     */
471
    protected function getMapper()
472 48
    {
473
        return $this->get('sulu.content.mapper');
474
    }
475
476
    /**
477
     * Delegates actions by given actionParameter, which can be retrieved from the request.
478
     *
479
     * @param string $actionParameter
480
     * @param object $document
481 1
     * @param string $locale
482
     */
483 1
    private function handleActionParameter($actionParameter, $document, $locale)
484 1
    {
485 1
        switch ($actionParameter) {
486 1
            case 'publish':
487
                $this->getDocumentManager()->publish($document, $locale);
488
                break;
489 1
        }
490
    }
491
492
    /**
493
     * Converts camel case string into normalized string with underscore.
494
     *
495
     * @param string $camel
496
     *
497
     * @return string
498
     */
499
    private function uncamelize($camel)
500
    {
501
        $camel = preg_replace(
502
            '/(?!^)[[:upper:]][[:lower:]]/',
503
            '$0',
504
            preg_replace('/(?!^)[[:upper:]]+/', '_$0', $camel)
505
        );
506
507
        return strtolower($camel);
508
    }
509
}
510