Completed
Pull Request — master (#1908)
by Basil
03:19 queued 56s
created

TagParser::parseTag()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 14
rs 9.7998
c 0
b 0
f 0
cc 4
nc 8
nop 2
1
<?php
2
3
namespace luya;
4
5
use Yii;
6
use luya\tag\TagMarkdownParser;
7
use yii\base\BaseObject;
8
9
/**
10
 * TagParser allows you to inject Tags and parse them.
11
 *
12
 * This is an additional concept to markdown, where you can inject your custom tags to parse. All tags must be an instance
13
 * of `luya\tag\BaseTag` and implement the `parse($value, $sub)` method in order to convert the input to your tag.
14
 *
15
 * Read more in the Guide [[concept-tags.md]].
16
 *
17
 * The identifier of the tag is not related to your tag, so you can configure the same tag as different names with multiple
18
 * purposes.
19
 *
20
 * To inject a created tag just use:
21
 *
22
 * ```php
23
 * TagParser::inject('tagname', ['class' => 'path\to\TagClass']);
24
 * ```
25
 *
26
 * To parse text with or without markdown use:
27
 *
28
 * ```php
29
 * TagParser::convert('Hello tagname[value](sub)');
30
 * TagParser::convertWithMarkdown('**Hello** tagname[value](sub)');
31
 * ```
32
 *
33
 * @author Basil Suter <[email protected]>
34
 * @since 1.0.0
35
 */
36
class TagParser extends BaseObject
37
{
38
    /**
39
     * @var string Base regular expression to determine function, values and value-sub informations.
40
     * @see https://regex101.com/r/hP9nJ1/1 - Online Regex tester
41
     */
42
    const REGEX = '/(?<function>[a-z]+)\[(?<value>.*?)\]((?<!\\\\)\((?<sub>.*?)(?<!\\\\)\))?/mi';
43
    
44
    private $tags = [
45
        'mail' => ['class' => 'luya\tag\tags\MailTag'],
46
        'tel' => ['class' => 'luya\tag\tags\TelTag'],
47
        'link' => ['class' => 'luya\tag\tags\LinkTag'],
48
    ];
49
    
50
    private static $_instance;
51
    
52
    /**
53
     * Inject a new tag with a given name and a configurable array config.
54
     *
55
     * @param string $name The name of the tag on what the tag should be found. Must be [a-z] chars.
56
     * @param string|array $config The configurable object context can be either a string with a class or a configurable array base on {{yii\base\BaseObject}} concept.
57
     */
58
    public static function inject($name, $config)
59
    {
60
        self::getInstance()->addTag($name, $config);
61
    }
62
    
63
    /**
64
     * Convert the CMS-Tags into HTML-Tags.
65
     *
66
     * @param string $text The content where the CMS-Tags should be found and convert into Html-Tags.
67
     * @return string The converted output of $text.
68
     */
69
    public static function convert($text)
70
    {
71
        return self::getInstance()->processText($text);
72
    }
73
    
74
    /**
75
     * Convert the CMS-Tags into HTMl-Tags and additional convert GFM Markdown into Html as well. The main purpose
76
     * of this method to fix the conflict between markdown and tag parser when using urls.
77
     *
78
     * @param string $text The content where the CMS-Tags should be found and convert into Html-Tags and Markdown Tags.
79
     * @return string the Converted output of $text.
80
     */
81
    public static function convertWithMarkdown($text)
82
    {
83
        return (new TagMarkdownParser())->parse(static::convert($text));
84
    }
85
    
86
    /**
87
     * Generate the instance for all registered tags.
88
     *
89
     * The main purpose of this method is to return all tag objects in admin context to provide help informations from the tags.
90
     *
91
     * @return \luya\tag\TagInterface
92
     */
93
    public static function getInstantiatedTagObjects()
94
    {
95
        $context = self::getInstance();
96
        foreach ($context->tags as $key => $config) {
97
            $context->instantiatTag($key);
98
        }
99
        
100
        return $context->tags;
101
    }
102
    
103
    /**
104
     * Get the TagParser object, create new if not exists
105
     * 
106
     * @return static
107
     */
108
    private static function getInstance()
109
    {
110
        if (self::$_instance === null) {
111
            self::$_instance = new self;
112
        }
113
    
114
        return self::$_instance;
115
    }
116
    
117
    /**
118
     * Internal method to add a tag into the tags array.
119
     */
120
    private function addTag($identifier, $tagObjectConfig)
121
    {
122
        $this->tags[$identifier] = $tagObjectConfig;
123
    }
124
125
    /**
126
     * Check if the given tag name exists.
127
     * 
128
     * @return boolean
129
     */
130
    private function hasTag($tag)
131
    {
132
        return isset($this->tags[$tag]);
133
    }
134
135
    /**
136
     * Create the tag instance (object) for a given tag name.
137
     */
138
    private function instantiatTag($tag)
139
    {
140
        if (!is_object($this->tags[$tag])) {
141
            $this->tags[$tag] = Yii::createObject($this->tags[$tag]);
142
            Yii::trace('tag parser object generated for:'. $tag, __CLASS__);
0 ignored issues
show
Deprecated Code introduced by
The method yii\BaseYii::trace() has been deprecated with message: since 2.0.14. Use [[debug()]] instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
143
        }
144
    }
145
    
146
    /**
147
     * Parse the given tag with context informations.
148
     * 
149
     * @return string Returns the parsed tag value.
150
     */
151
    private function parseTag($tag, $context)
152
    {
153
        // ensure tag is an object
154
        $this->instantiatTag($tag);
155
        // extract context
156
        $value = isset($context['value']) ? $context['value'] : false;
157
        $sub = isset($context['sub']) ? $context['sub'] : false;
158
        // the sub value can contain escaped values, those values must be parsed back into the original state.
159
        if ($sub) {
160
            $sub = str_replace(['\)', '\('], [')', '('], $sub);
161
        }
162
        // run parse method inside the tag object.
163
        return $this->tags[$tag]->parse($value, $sub);
164
    }
165
166
    /**
167
     * Process a given text.
168
     * 
169
     * + This will find all tag based expressions inside the text
170
     * + instantiate the tag if the alias exists.
171
     * + parse the tag and modify the input $text
172
     * 
173
     * @param string $text The input text   
174
     * @return string The parsed text
175
     */
176
    private function processText($text)
177
    {
178
        // verify if content is a string otherwhise just return the original provided content
179
        if (!is_string($text) || empty($text)) {
180
            return $text;
181
        }
182
        // find all tags based on the REGEX expression
183
        preg_match_all(static::REGEX, $text, $results, PREG_SET_ORDER);
184
        // foreach all the results matches the regex
185
        foreach ($results as $row) {
186
            // When value is empty (can be caused by using `link[]` we have to skip this item.
187
            if (empty($row['value'])) {
188
                continue;
189
            }
190
            // extract tag name from regex
191
            $tag = $row['function'];
192
            if ($this->hasTag($tag)) {
193
                $replace = $this->parseTag($tag, $row);
194
                $text = preg_replace('/'.preg_quote($row[0], '/').'/mi', $replace, $text, 1);
195
            }
196
        }
197
        
198
        return $text;
199
    }
200
}
201