1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Finds {@link DataObject} instances using certain shortcodes |
4
|
|
|
* by fulltext-querying only fields which are capable of parsing shortcodes. |
5
|
|
|
* Effectively the reverse of "link tracking", |
6
|
|
|
* which updates this relation on write rather than fetching it on demand. |
7
|
|
|
* |
8
|
|
|
* Doesn't scale to millions of pages due to triggering a potentially unindexed LIKE |
9
|
|
|
* search across dozens of columns and tables - but for a couple of hundred pages |
10
|
|
|
* and occasionally use its a feasible solution. |
11
|
|
|
*/ |
12
|
|
|
class ShortCodeRelationFinder |
13
|
|
|
{ |
14
|
|
|
|
15
|
|
|
/** |
16
|
|
|
* @var String Regex matching a {@link DBField} class name which is shortcode capable. |
17
|
|
|
* |
18
|
|
|
* This should really look for implementors of a ShortCodeParseable interface, |
19
|
|
|
* but we can't extend the core Text and HTMLText class |
20
|
|
|
* on existing like SiteTree.Content for this. |
21
|
|
|
*/ |
22
|
|
|
protected $fieldSpecRegex = '/^(HTMLText)/'; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* @param String Shortcode index number to find |
26
|
|
|
* @return array IDs |
27
|
|
|
*/ |
28
|
|
|
public function findPageIDs($number) |
29
|
|
|
{ |
30
|
|
|
$list = $this->getList($number); |
31
|
|
|
$found = $list->column(); |
32
|
|
|
return $found; |
33
|
|
|
} |
34
|
|
|
|
35
|
|
|
public function findPageCount($number) |
36
|
|
|
{ |
37
|
|
|
$list = $this->getList($number); |
38
|
|
|
return $list->count(); |
39
|
|
|
} |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* @param int $number |
43
|
|
|
* @return DataList |
44
|
|
|
*/ |
45
|
|
|
public function getList($number) |
46
|
|
|
{ |
47
|
|
|
$number = (int) $number; |
48
|
|
|
$list = DataList::create('SiteTree'); |
49
|
|
|
$where = array(); |
50
|
|
|
$fields = $this->getShortCodeFields('SiteTree'); |
51
|
|
|
$shortcode = DMS::inst()->getShortcodeHandlerKey(); |
52
|
|
|
foreach ($fields as $ancClass => $ancFields) { |
53
|
|
|
foreach ($ancFields as $ancFieldName => $ancFieldSpec) { |
54
|
|
|
if ($ancClass != "SiteTree") { |
55
|
|
|
$list = $list->leftJoin($ancClass, '"'.$ancClass.'"."ID" = "SiteTree"."ID"'); |
56
|
|
|
} |
57
|
|
|
$where[] = "\"$ancClass\".\"$ancFieldName\" LIKE '%[{$shortcode},id=$number]%'"; //."%s" LIKE ""', |
58
|
|
|
} |
59
|
|
|
} |
60
|
|
|
|
61
|
|
|
$list = $list->where(implode(' OR ', $where)); |
62
|
|
|
return $list; |
63
|
|
|
} |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* Returns a filtered list of fields which could contain shortcodes. |
67
|
|
|
* |
68
|
|
|
* @param String |
69
|
|
|
* @return Array Map of class names to an array of field names on these classes. |
70
|
|
|
*/ |
71
|
|
|
public function getShortcodeFields($class) |
72
|
|
|
{ |
73
|
|
|
$fields = array(); |
74
|
|
|
$ancestry = array_values(ClassInfo::dataClassesFor($class)); |
75
|
|
|
|
76
|
|
|
foreach ($ancestry as $ancestor) { |
77
|
|
|
if (ClassInfo::classImplements($ancestor, 'TestOnly')) { |
78
|
|
|
continue; |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
$ancFields = DataObject::custom_database_fields($ancestor); |
82
|
|
|
if ($ancFields) { |
|
|
|
|
83
|
|
|
foreach ($ancFields as $ancFieldName => $ancFieldSpec) { |
84
|
|
|
if (preg_match($this->fieldSpecRegex, $ancFieldSpec)) { |
85
|
|
|
if (!@$fields[$ancestor]) { |
86
|
|
|
$fields[$ancestor] = array(); |
87
|
|
|
} |
88
|
|
|
$fields[$ancestor][$ancFieldName] = $ancFieldSpec; |
89
|
|
|
} |
90
|
|
|
} |
91
|
|
|
} |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
return $fields; |
95
|
|
|
} |
96
|
|
|
} |
97
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.