Passed
Push — master ( 40ca60...430c98 )
by Sebastian
02:56
created

Mailcode_Parser_Safeguard_Formatter_HTMLHighlighting_Location   A

Complexity

Total Complexity 16

Size/Duplication

Total Lines 128
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 33
c 1
b 0
f 0
dl 0
loc 128
rs 10
wmc 16

7 Methods

Rating   Name   Duplication   Size   Complexity  
A getParentTags() 0 20 3
A getAdjustedText() 0 3 1
A init() 0 9 1
A isInExcludedTag() 0 13 3
A requiresAdjustment() 0 8 3
A getHaystackBefore() 0 17 2
A isInTagAttributes() 0 13 3
1
<?php
2
/**
3
 * File containing the {@see Mailcode_Parser_Safeguard_Formatter_SingleLines_Placeholder} class.
4
 *
5
 * @package Mailcode
6
 * @subpackage Parser
7
 * @see Mailcode_Parser_Safeguard_Formatter_SingleLines_Placeholder
8
 */
9
10
declare(strict_types=1);
11
12
namespace Mailcode;
13
14
/**
15
 * Detects whether the placeholder location can be highlighted or not.
16
 *
17
 * @package Mailcode
18
 * @subpackage Parser
19
 * @author Sebastian Mordziol <[email protected]>
20
 * 
21
 * @property Mailcode_Parser_Safeguard_Formatter_HTMLHighlighting $formatter
22
 */
23
class Mailcode_Parser_Safeguard_Formatter_HTMLHighlighting_Location extends Mailcode_Parser_Safeguard_FormatterLocation
24
{
25
    const COMMAND_MARKER = '__MAILCODE_COMMAND__';
26
    
27
   /**
28
    * @var array<int,array<int,string>>
29
    */
30
    private $tagAncestry;
31
    
32
    protected function init() : void
33
    {
34
        $haystack = $this->getHaystackBefore();
35
        
36
        // Get a list of all HTML tags before the command, opening and closing.
37
        $matches = array();
38
        preg_match_all('%<\s*(/?)\s*([a-z][a-z0-9]*)\s*([^<>]*)>%ix', $haystack, $matches, PREG_PATTERN_ORDER);
39
        
40
        $this->tagAncestry = $matches;
41
    }
42
    
43
    protected function getAdjustedText(): string
44
    {
45
        return '<mailcode:highlight>'.$this->location->getPlaceholderString().'</mailcode:highlight>';
46
    }
47
48
    public function requiresAdjustment(): bool
49
    {
50
        if($this->isInTagAttributes() || $this->isInExcludedTag()) 
51
        {
52
           return false; 
53
        }
54
        
55
        return true;
56
    }
57
    
58
   /**
59
    * Retrieves the part of the subject string that comes
60
    * before the command.
61
    * 
62
    * @return string
63
    */
64
    private function getHaystackBefore() : string
65
    {
66
        $pos = $this->location->getStartPosition();
67
        
68
        // at the beginning of the document? Sure, we can highlight this.
69
        if($pos === 0)
70
        {
71
            return '';
72
        }
73
        
74
        $subject = $this->location->getSubjectString();
75
        
76
        // We add a command marker and a closing tag bracket,
77
        // so that is the command is in a tag's attributes,
78
        // the tags ancestry can detect the tag as a parent 
79
        // tag, including the marker in the attributes string.
80
        return mb_substr($subject, 0, $pos).self::COMMAND_MARKER.'>';
81
    }
82
    
83
   /**
84
    * Whether the command is nested in one of the tags
85
    * that have been added to the list of excluded tags.
86
    * 
87
    * @return bool
88
    */
89
    private function isInExcludedTag() : bool
90
    {
91
        $tagNames = $this->getParentTags();
92
        
93
        foreach($tagNames as $tagName)
94
        {
95
            if($this->formatter->isTagExcluded($tagName))
96
            {
97
                return true;
98
            }
99
        }
100
        
101
        return false;
102
    }
103
    
104
   /**
105
    * Retrieves a list of the command's parent HTML tags, from
106
    * highest to lowest.
107
    * 
108
    * @return string[]
109
    */
110
    private function getParentTags() : array
111
    {
112
        // Create a stack of all direct parent tags of the command.
113
        // Handles closing tags as well.
114
        $stack = array();
115
        foreach($this->tagAncestry[2] as $idx => $tagName)
116
        {
117
            $closing = $this->tagAncestry[1][$idx] === '/';
118
            
119
            if($closing)
120
            {
121
                array_pop($stack);
122
            }
123
            else
124
            {
125
                $stack[] = $tagName;
126
            }
127
        }
128
        
129
        return $stack;
130
    }
131
    
132
   /**
133
    * Checks whether the command is located within the attributes
134
    * of an HTML tag.
135
    * 
136
    * @return bool
137
    */
138
    private function isInTagAttributes() : bool
139
    {
140
        // This check is easy: if the command is in the attribute
141
        // of any of the tags, we will find the command marker in there.
142
        foreach($this->tagAncestry[3] as $attributes)
143
        {
144
            if(strstr($attributes, self::COMMAND_MARKER))
145
            {
146
                return true;
147
            }
148
        }
149
        
150
        return false;
151
    }
152
}
153