Issues (10)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Taxonomy/Taxonomy.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php namespace Rocket\Taxonomy;
2
3
use Illuminate\Contracts\Cache\Repository as CacheRepository;
4
use Rocket\Taxonomy\Model\Hierarchy;
5
use Rocket\Taxonomy\Model\TermContainer;
6
use Rocket\Taxonomy\Model\TermData;
7
use Rocket\Taxonomy\Model\Vocabulary;
8
use Rocket\Taxonomy\Repositories\TermHierarchyRepositoryInterface as TermHieraRep;
9
use Rocket\Taxonomy\Repositories\TermRepositoryInterface as TermRep;
10
use Rocket\Translation\Support\Laravel5\Facade as I18N;
11
12
/**
13
 * The Taxonomy base class handles the cache and the creation/modification of terms
14
 */
15
class Taxonomy implements TaxonomyInterface
16
{
17
    /**
18
     * @var array Terms internal cache
19
     */
20
    public $terms = [];
21
22
    /**
23
     * @var array List of vocabularies by ID
24
     */
25
    protected $vocabularyById = [];
26
27
    /**
28
     * @var array List of vocabularies by Name
29
     */
30
    protected $vocabularyByName = [];
31
32
    /**
33
     * @var CacheRepository The repository to cache Terms
34
     */
35
    protected $cache;
36
37
    /**
38
     * @var TermRep The repository to load Terms
39
     */
40
    protected $termRepository;
41
42
    /**
43
     * @var TermHieraRep The repository to handle terms hierarchies
44
     */
45
    protected $termHierarchyRepository;
46
47
    /**
48
     * Initialize the Taxonomy system, Loads all existing vocabularies
49
     *
50
     * @param CacheRepository $cache The cache repository
51
     * @param TermRep $termRepository The term retrieval repository
52
     * @param TermHieraRep $termHierarchyRepository The term hierarchy repository
53
     */
54
    public function __construct(CacheRepository $cache, TermRep $termRepository, TermHieraRep $termHierarchyRepository)
55
    {
56
        $this->termRepository = $termRepository;
57
        $this->termHierarchyRepository = $termHierarchyRepository;
58
        $this->cache = $cache;
59 192
60
        // Get the list of vocabularies
61 192
        $vocs = $cache->remember('Rocket::Taxonomy::Vocabularies', 60 * 24 * 7, function () {
62 192
            return Vocabulary::all();
63 192
        });
64
65
        // Initialize the search for terms
66
        foreach ($vocs as $v) {
67 192
            $this->vocabularyByName[$v->machine_name] = $this->vocabularyById[$v->id] = $v;
68 192
        }
69
    }
70
71 192
    /**
72 186
     * Is this vocabulary translatable ?
73 192
     *
74 192
     * @param int|string $vid
75
     * @return bool
76
     */
77
    public function isTranslatable($vid)
78
    {
79
        if (!is_numeric($vid)) {
80
            $vid = $this->vocabulary($vid);
81
        }
82 180
83
        return $this->vocabularyById[$vid]->isTranslatable();
84 180
    }
85 3
86 3
    /**
87
     * Get the internal language for the vocabulary
88 180
     *
89
     * This will return the language_id if the vocabulary is translated or 1 if it's not
90
     *
91
     * @param int|string $vocabulary_id
92
     * @param int $language_id
93
     * @return int|null
94
     */
95
    public function getLanguage($vocabulary_id, $language_id = null)
96
    {
97
        if (!$this->isTranslatable($vocabulary_id)) {
98
            return 1;
99
        }
100 177
101
        if ($language_id === null) {
102 177
            return I18N::getCurrentId();
103 33
        }
104
105
        return $language_id;
106 150
    }
107 150
108
    /**
109
     * Get a vocabulary by name or ID
110 150
     *
111
     *     Taxonomy::vocabulary(1);
112
     *     returns 'tags'
113
     *
114
     *     Taxonomy::vocabulary('tags');
115
     *     returns 1
116
     *
117
     * @param int|string $key
118
     * @return mixed
119
     */
120
    public function vocabulary($key)
121
    {
122
        if (is_numeric($key)) {
123
            return $this->vocabularyById[$key]->machine_name;
124
        }
125 186
126
        return $this->vocabularyByName[$key]->id;
127 186
    }
128 3
129
    /**
130
     * Get all vocabularies with keys as id's
131 186
     *
132
     * @return array
133
     */
134
    public function vocabularies()
135
    {
136
        return $this->vocabularyById;
137
    }
138
139 6
    /**
140
     * Get a term with all translations
141 6
     *
142
     * @param int $term_id The term's ID
143
     * @param bool $from_cache Should we take this term from cache or request a fresh one ?
144
     * @return Term
145
     */
146
    public function getTerm($term_id, $from_cache = true)
147
    {
148
        if ($from_cache && array_key_exists($term_id, $this->terms)) {
149
            return $this->terms[$term_id];
150
        }
151 39
152
        $data = $this->termRepository->getTerm($term_id);
153 39
        $this->terms[$term_id] = $data;
154 3
155
        return $data;
156
    }
157 39
158 39
    /**
159
     * Remove a term from the cache
160 39
     *
161
     * @param int $term_id
162
     * @return bool
163
     */
164
    public function uncacheTerm($term_id)
165
    {
166
        if (array_key_exists($term_id, $this->terms)) {
167
            unset($this->terms[$term_id]);
168
        }
169 174
170
        return $this->termRepository->uncacheTerm($term_id);
171 174
    }
172 3
173 3
    /**
174
     * Get all paths for a term
175 174
     *
176
     * @param int $term_id
177
     * @return array<array<int>>
178
     */
179
    public function getAncestryPaths($term_id)
180
    {
181
        return $this->termHierarchyRepository->getAncestryPaths($term_id);
182
    }
183
184 12
    /**
185
     * Get all paths for a term
186 12
     *
187
     * @param int $term_id
188
     * @return array<array<int>>
189
     */
190
    public function getDescentPaths($term_id)
191
    {
192
        return $this->termHierarchyRepository->getDescentPaths($term_id);
193
    }
194
195 12
    /**
196
     * Get the complete graph
197 12
     * @param int $term_id
198
     * @return array
199
     */
200
    public function getAncestryGraph($term_id)
201
    {
202
        return $this->termHierarchyRepository->getAncestryGraph($term_id);
203
    }
204
205 3
    /**
206
     * Get the complete graph
207 3
     * @param int $term_id
208
     * @return array
209
     */
210
    public function getDescentGraph($term_id)
211
    {
212
        return $this->termHierarchyRepository->getDescentGraph($term_id);
213
    }
214
215 3
    /**
216
     * Add one parent to a term
217 3
     *
218
     * @param int $term_id
219
     * @param int $parent_id
220
     */
221
    public function addParent($term_id, $parent_id)
222
    {
223
        $this->testCanAddParents($term_id, 1);
224
225
        $this->termHierarchyRepository->addParent($term_id, $parent_id);
226 111
    }
227
228 111
    /**
229
     * Add a list of parents to a term
230 108
     *
231 108
     * @param int $term_id
232
     * @param array<integer> $parent_ids
233
     */
234
    public function addParents($term_id, array $parent_ids)
235
    {
236
        $this->testCanAddParents($term_id, count($parent_ids));
237
238
        foreach ($parent_ids as $id) {
239 6
            $this->termHierarchyRepository->addParent($term_id, $id);
240
        }
241 6
    }
242
243 6
    /**
244 6
     * Test if the term can have more parents
245 6
     *
246 6
     * @param int $term_id
247
     * @param int $count
248
     * @throws \RuntimeException
249
     */
250
    protected function testCanAddParents($term_id, $count)
251
    {
252
        $vocabulary = (new Vocabulary())->getTable();
253
        $term = (new TermContainer())->getTable();
254
        $v = Vocabulary::select('hierarchy', 'name')
255 114
            ->where("$term.id", $term_id)
256
            ->join($term, 'vocabulary_id', '=', "$vocabulary.id")
257 114
            ->first();
258 114
259 114
        if ($v->hierarchy == 0) {
260 114
            throw new \RuntimeException("Cannot add a parent in vocabulary '$v->name'");
261 114
        }
262 114
263
        if (($v->hierarchy == 1 && Hierarchy::where('term_id', $term_id)->count() > 0)
264 114
            || ($v->hierarchy == 1 && $count > 1)) {
265 3
            throw new \RuntimeException("Cannot have more than one parent in vocabulary '$v->name'");
266
        }
267
    }
268 111
269 111
    /**
270 3
     * Remove the parents form this term
271
     *
272 111
     * @param int $term_id
273
     * @return bool
274
     */
275
    public function unsetParents($term_id)
276
    {
277
        return $this->termHierarchyRepository->unsetParents($term_id);
278
    }
279
280 6
    /**
281
     * Get all the terms of a vocabulary
282 6
     *
283
     * @param int $vocabulary_id
284
     * @return array
285
     */
286
    public function getTermsForVocabulary($vocabulary_id)
287
    {
288
        return $this->cache->remember(
289
            'Rocket::Taxonomy::Terms::' . $vocabulary_id,
290
            60,
291 3
            function () use ($vocabulary_id) {
292
                $terms = TermContainer::where('vocabulary_id', $vocabulary_id)->get(['id']);
293 3
294 3
                $results = [];
295 3
                if (!empty($terms)) {
296 3
                    foreach ($terms as $term) {
297 3
                        $results[] = $term->id;
298
                    }
299 3
                }
300 3
301 3
                return $results;
302 3
            }
303 3
        );
304 3
    }
305
306 3
    /**
307
     * Search a specific term, if it doesn't exist, returns false
308 3
     *
309
     * @param  string $term
310
     * @param  int $vocabulary_id
311
     * @param  int $language_id
312
     * @param  array $exclude
313
     * @return int|null
314
     */
315
    public function searchTerm($term, $vocabulary_id, $language_id = null, $exclude = [])
316
    {
317
        $language_id = $this->getLanguage($vocabulary_id, $language_id);
318
319
        $term = trim($term);
320 174
        if ($term == '') {
321
            return;
322 174
        }
323
324 174
        $query = TermData::select('taxonomy_terms.id')
325 174
            ->join('taxonomy_terms', 'taxonomy_terms.id', '=', 'taxonomy_terms_data.term_id')
326 3
            ->where('taxonomy_terms.vocabulary_id', $vocabulary_id)
327
            ->where('taxonomy_terms_data.language_id', $language_id)
328
            ->where('taxonomy_terms_data.title', $term);
329 174
330 174
        if (count($exclude)) {
331 174
            $query->whereNotIn('taxonomy_terms.id', $exclude);
332 174
        }
333 174
334
        return $query->value('id');
335 174
    }
336 3
337 3
    /**
338
     * Returns the id of a term, if it doesn't exist, creates it.
339 174
     *
340
     * @param  string $title
341
     * @param  int $vocabulary_id
342
     * @param  int $language_id
343
     * @param  int $type
344
     * @return bool|int
345
     */
346
    public function getTermId($title, $vocabulary_id, $language_id = null, $type = 0)
347
    {
348
        $title = trim($title);
349
        if ($title == '') {
350
            return false;
351 177
        }
352
353 177
        if (!is_numeric($vocabulary_id)) {
354 177
            $vocabulary_id = $this->vocabulary($vocabulary_id);
355 3
        }
356
357
        $language_id = $this->getLanguage($vocabulary_id, $language_id);
358 174
359 15
        $search = $this->searchTerm($title, $vocabulary_id, $language_id);
360 15
        if ($search !== null) {
361
            return $search;
362 174
        }
363
364 174
        // Add term
365 174
        $term = new TermContainer(['vocabulary_id' => $vocabulary_id]);
366 21
367
        if ($type !== 0) {
368
            $term->type = $type;
369
        }
370 174
        $term->save();
371
372 174
        // Add translation
373 6
        $translation = [
374 6
            'language_id' => $language_id,
375 174
            'title' => $title,
376
        ];
377
        $term->translations()->save(new TermData($translation));
378
379 174
        return $term->id;
380 174
    }
381 174
382 174
    /**
383
     * Adds one or more tags and returns an array of id's
384 174
     *
385
     * @param array $taxonomies
386
     * @return array
387
     */
388
    public function getTermIds($taxonomies)
389
    {
390
        $tags = [];
391
        foreach ($taxonomies as $voc => $terms) {
392
            $vocabulary_id = $this->vocabulary($voc);
393 30
            $exploded = is_array($terms) ? $terms : explode(',', $terms);
394
395 30
            foreach ($exploded as $term) {
396 30
                $result = $this->getTermId($term, $vocabulary_id);
397 30
                if ($result) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $result of type false|integer is loosely compared to true; this is ambiguous if the integer can be zero. 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 integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
398 30
                    $tags[] = $result;
399
                }
400 30
            }
401 30
        }
402 30
403 30
        return $tags;
404 30
    }
405
}
406