Completed
Push — 4-cactus ( 59c50f...21a27c )
by Alberto
02:40
created

Core/src/Model/Behavior/UniqueNameBehavior.php (1 issue)

1
<?php
2
/**
3
 * BEdita, API-first content management framework
4
 * Copyright 2016 ChannelWeb Srl, Chialab Srl
5
 *
6
 * This file is part of BEdita: you can redistribute it and/or modify
7
 * it under the terms of the GNU Lesser General Public License as published
8
 * by the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * See LICENSE.LGPL or <http://gnu.org/licenses/lgpl-3.0.html> for more details.
12
 */
13
14
namespace BEdita\Core\Model\Behavior;
15
16
use Cake\Datasource\EntityInterface;
17
use Cake\Event\Event;
18
use Cake\ORM\Behavior;
19
use Cake\ORM\TableRegistry;
20
use Cake\Utility\Text;
21
22
/**
23
 * UniqueName behavior
24
 *
25
 * Creates or updates a unique name of objects (see `objects.uname` field).
26
 *
27
 * Unique name is created tyipically from object title or from other object properties in case of missing title.
28
 * An object type may impose custom rule.
29
 * Name must be unique inside current project.
30
 *
31
 * @since 4.0.0
32
 */
33
class UniqueNameBehavior extends Behavior
34
{
35
    /**
36
     * Max regenerate iterations to avoid duplicates.
37
     *
38
     * @var int
39
     */
40
    const UNAME_MAX_REGENERATE = 10;
41
42
    /**
43
     * Default configuration.
44
     *
45
     * Possible keys are:
46
     *  - 'sourceField' field value to use for unique name creation
47
     *  - 'prefix' constant prefix to use
48
     *  - 'replacement' character replacement for space
49
     *  - 'separator' seaparator of `hash` suffix
50
     *  - 'hashlength' `hash` suffix length
51
     *  - 'generator' callable function for unique name generation, if set all other keys are ignored
52
     *
53
     * @var array
54
     */
55
    protected $_defaultConfig = [
56
        'sourceField' => 'title',
57
        'prefix' => '',
58
        'replacement' => '-',
59
        'separator' => '_',
60
        'hashlength' => 6,
61
        'generator' => null,
62
    ];
63
64
    /**
65
     * Setup unique name of a BEdita object $entity if a new entity is created
66
     * If no custom generator is used unique name is built using a friendly
67
     * url `slug` version of a `sourceField` (default 'title')
68
     *
69
     * @param \Cake\Datasource\EntityInterface $entity The entity to save
70
     * @return void
71
     */
72
    public function uniqueName(EntityInterface $entity)
73
    {
74
        $uname = $entity->get('uname');
75
        if (empty($uname)) {
76
            $uname = $this->generateUniqueName($entity);
77
        }
78
        $count = 0;
79
        while ($this->uniqueNameExists($uname, $entity->get('id'))
80
            && ($count++ < self::UNAME_MAX_REGENERATE)) {
81
            $uname = $this->generateUniqueName($entity, true);
82
        }
83
84
        $entity->set('uname', $uname);
85
    }
86
87
    /**
88
     * Generate unique name string from $config parameters.
89
     * If $regenerate parameter is true, random hash is added to uname string.
90
     * A 'callable' item is called if set in config('generator') instead of generateUniqueName(...)
91
     *
92
     * @param \Cake\Datasource\EntityInterface $entity The entity to save
93
     * @param bool $regenerate if true it adds hash string to uname
94
     * @param array $cfg Optional config parameters to override defaults
95
     * @return string uname
96
     */
97
    public function generateUniqueName(EntityInterface $entity, $regenerate = false, array $cfg = [])
98
    {
99
        $config = array_merge($this->getConfig(), $cfg);
100
        $generator = $config['generator'];
101
        if (is_callable($generator)) {
102
            return $generator($entity, $regenerate);
103
        }
104
        $fieldValue = $entity->get($config['sourceField']);
105
        if (empty($fieldValue)) {
106
            $fieldValue = (string)$entity->get('type');
107
            $regenerate = true;
108
        }
109
110
        return $this->uniqueNameFromValue($fieldValue, $regenerate);
111
    }
112
113
    /**
114
     * Generate unique name string from $config parameters.
115
     * If $regenerate parameter is true, random hash is added to uname string.
116
     *
117
     * @param string $value String to use in unique name creation
118
     * @param bool $regenerate if true it adds hash string to uname
119
     * @param array $cfg parameters to create unique name
120
     * @return string uname
121
     */
122
    public function uniqueNameFromValue($value, $regenerate = false, array $cfg = [])
123
    {
124
        $config = array_merge($this->getConfig(), $cfg);
125
        $uname = $config['prefix'] . Text::slug($value, $config['replacement']);
126
        if ($regenerate) {
127
            $hash = Text::uuid();
128
            $hash = str_replace('-', '', $hash);
129
            if (!empty($config['hashlength'])) {
130
                $hash = substr($hash, 0, $config['hashlength']);
131
            }
132
            $uname .= $config['separator'] . $hash;
133
        }
134
135
        return strtolower($uname);
136
    }
137
138
    /**
139
     * Verify $uname is unique
140
     *
141
     * @param string $uname to check
142
     * @param int $id object id to exclude from check
143
     * @return bool
144
     */
145
    public function uniqueNameExists($uname, $id = null)
146
    {
147
        $options = ['uname' => $uname];
148
        if (!empty($id)) {
149
            $options['id <>'] = $id;
150
        }
151
152
        return TableRegistry::get('Objects')->exists($options);
153
    }
154
155
    /**
156
     * Setup unique name for a BEdita object represented by $entity
157
     * through `uniqueName()` method
158
     *
159
     * @param \Cake\Event\Event $event The event dispatched
160
     * @param \Cake\Datasource\EntityInterface $entity The entity to save
161
     * @return void
162
     */
163
    public function beforeSave(Event $event, EntityInterface $entity)
0 ignored issues
show
The parameter $event is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

163
    public function beforeSave(/** @scrutinizer ignore-unused */ Event $event, EntityInterface $entity)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
164
    {
165
        $this->uniqueName($entity);
166
    }
167
}
168