Passed
Push — develop ( 2749c8...d768fa )
by Dylan
02:40
created

SEOToolboxControllerExtension::addAutomatedLinks()   C

Complexity

Conditions 7
Paths 6

Size

Total Lines 31
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 31
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 17
nc 6
nop 0
1
<?php
2
/**
3
 * Plugin: SEOToolbox
4
 * Author: Dylan Grech
5
 * Copyright: 2016
6
 *
7
 * SEOtoolbox Controller Extension decorates the Content Controller
8
 * to add the auotamted links to a page where needed
9
 *
10
 * @see AutomatedLink
11
 */
12
class SEOToolboxControllerExtension extends Extension {
1 ignored issue
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
13
14
    private $maxLinksPerPage;
15
    private $settings       = null;
16
    private $linkCount      = 0;
17
    private $addLinks       = true;
18
    private $excludeTags    = array();
19
    private $maxLinks       = 0;
20
21
    public function index(){
22
        $this->addAutomatedLinks();
23
24
        // If we have a crawl request check the CrawlID so we're sure we didn't hit another SS site running our module
25
        if( $crawl_id = $this->owner->request->getHeader('X-Crawl-Id') ){
26
            return( $crawl_id == GlobalAutoLinkSettings::get_current()->CrawlID )
27
                ? $this->crawl_response()
28
                : $this->owner->redirect(SEOTestSiteTreeController::getPermissionDeniedPage()->Link());
29
        }
30
31
        return array();
32
    }
33
34
    private function crawl_response(){
35
        // Encoded version to detect which fields are being used
36
        $customize = array();
37
        $dbFields  = Config::inst()->get($this->owner->ClassName, 'db');
38
        if(is_array($dbFields)) {
39
            foreach ( $dbFields as $field => $type) {
40
                if (strtolower($type) == 'htmltext') {
41
                    $data = ($this->owner->hasMethod($field)) ? $this->owner->$field() : $this->owner->$field;
42
                    if($data){
43
                        $tmp = new HTMLText('tmp');
44
                        $tmp->setValue($data);
45
                        $data = base64_encode($tmp->forTemplate());
46
                        $customize[$field] = "[**[$field]**[$data]**]";
47
                    }
48
                }
49
            }
50
        }
51
52
        if (in_array($this->owner->ClassName, ClassInfo::subclassesFor('ErrorPage'))) {
53
            header("HTTP/1.0 405 Instance of ErrorPage");
54
            die();
1 ignored issue
show
Coding Style Compatibility introduced by
The method crawl_response() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
55
        }
56
57
        // Clean out the html before sending it back to minimize response size
58
        die(
1 ignored issue
show
Coding Style Compatibility introduced by
The method crawl_response() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
59
            preg_replace(array(
60
                '/<style(.*?)[>]/im',
61
                '/<(script|noscript)(.*?)<\/(script|noscript)[>]/im',
62
                '/<!--(.*?)-->/im',
63
            ), '', $this->owner->customise($customize)->render())
64
        );
65
    }
66
67
    /**
68
     * Get the global settings and check if we should be adding
69
     * links to this page
70
     *
71
     * @return GlobalAutoLinkSettings|false
72
     */
73
    private function getSettings() {
74
        if ($this->settings === null) {
75
            $this->settings = GlobalAutoLinkSettings::get_current();
76
            if (!$this->settings) return $this->addLinks = false;
77
78
            $this->excludeTags = (array) $this->settings->ExcludeTags();
79
            $this->maxLinks = (int) ($this->settings->MaxLinksPerPage) ? $this->settings->MaxLinksPerPage : PHP_INT_MAX;
80
81
            if (!in_array($this->owner->ClassName, $this->settings->AllowedIn())) $this->addLinks = false;
82
        }
83
84
        return $this->settings;
85
    }
86
87
    /**
88
     * Goes through all the automated link settings and adds
89
     * the links where necessary
90
     *
91
     * @return void
92
     */
93
    public function addAutomatedLinks(){
94
        if( GlobalAutoLinkSettings::$enabled && $this->owner->class != 'RedirectorPage' ) {
95
            $this->getSettings();
96
            if( !$this->addLinks ) {
97
                return;
98
            }
99
100
            foreach( $this->getSettings()->IncludeInFields() as $field ){
101
                // Check that the field provided by user exists in this object, is of type HTMLText and has content
102
                if( AutomatedLink::isFieldParsable( $this->owner->data(), $field ) ){
103
104
                    // Create dummy object so we can parse the HTML
105
                    $dummy = new HTMLText( $field );
106
                    $dummy->setValue( $this->owner->$field );
107
                    // Create DOMDocument Object
108
                    $content = mb_convert_encoding( $dummy->forTemplate(), 'html-entities', GlobalAutoLinkSettings::$encoding );
109
                    $dom = AutomatedLink::constructDOMDocument($content);
110
111
                    // Check current link count and if it's already exceeded do nothing
112
                    $this->linkCount += (int) $dom->getElementsByTagName( 'a' )->length;
113
                    if( $this->linkCount >= $this->maxLinks ) {
114
                        return;
115
                    }
116
117
                    $parsed = $this->parseField( $dom, $field );
118
                    $this->owner->data()->$field = $parsed;
119
                    $this->owner->$field         = $parsed;
120
                }
121
            }
122
        }
123
    }
124
125
    /**
126
     * Parse the provided field and add the necessary links
127
     *
128
     * @param DOMDocument $html
129
     * @param String $field
130
     * @return string
131
     */
132
    private function parseField( DOMDocument $html, $field ){
133
        $this->owner->extend( 'beforeParseField', $html, $field );
134
135
        // Remove Tags from Content we wown't be using
136
        $excluded = array();
137
        foreach( $this->excludeTags as $eTag ){
138
            while( $tags = $html->getElementsByTagName( $eTag ) ){
139
                if( !$tags->length ) break 1;
140
                $tag	= $tags->item(0);
141
                $value  = $html->saveHTML( $tag );
142
                $key    = (string) crc32( $value );
143
144
                // Convert back children nodes of this node if they were already hashed
145
                $excluded[$key] = str_replace( array_keys( $excluded ), array_values( $excluded ), $value );
146
147
                $tag->parentNode->replaceChild( $html->createTextNode( $key ), $tag );
148
            }
149
        }
150
151
        $body    = (string)$html->saveHTML( $html->getElementsByTagName('body')->item(0) );
152
        $content = preg_replace( array( '/\<body\>/is', '/\<\/body\>/is' ), '', $body, 1 );
153
154
        // Create the links
155
        $links = AutomatedLink::get()->sort('Priority');
156
        foreach( $links as $link ){
157
            // Check if self-linking is allowed and if current pagetype is allowed
158
            if( !$link->canBeAdded( $this->owner, $field ) ) continue;
159
160
            $max    = (int) ( $link->MaxLinksPerPage > 0 ) ? $link->MaxLinksPerPage : PHP_INT_MAX;
161
            $escape = (string) preg_quote( $link->Phrase, '/' );
162
            $regex  = (string) ( $link->CaseSensitive ) ? "/(\b{$escape}\b)/" : "/(\b{$escape}\b)/i";
163
164
            // Count the matches
165
            preg_match_all( $regex, $content, $count );
166
            $count = ( is_array( $count ) && isset( $count[0] ) ) ? count( $count[0] ) : 0;
167
            if( $count < 1 ) continue;
168
169
            if( isset( $this->maxLinksPerPage[ $link->ID ] ) )
170
                $max -= $this->maxLinksPerPage[ $link->ID ];
171
            else
172
                $this->maxLinksPerPage[ $link->ID ] = 0;
173
174
            for( $x = 0; $x < $count; $x++ ){
175
                // Stop adding links if we reached the link or page limit
176
                if( $x >= $max || $this->linkCount >= $this->maxLinks ) break;
177
178
                // Check if there is anything else to replace else stop
179
                preg_match( $regex, $content, $match );
180
                if( !is_array( $match ) || !count( $match ) ) break;
181
182
                if( !$html = (string) $link->getHTML( $match[0] ) ) continue;
183
                $key              = (string) crc32( $html );
184
                $excluded[ $key ] = (string) $html;
185
186
                $content = preg_replace( $regex, $key, $content, 1 );
187
                $this->linkCount++;
188
                $this->maxLinksPerPage[ $link->ID ]++;
189
            }
190
191
            // Stop Adding links if we reached the page limit
192
            if( $this->linkCount >= $this->maxLinks ) break;
193
        }
194
195
        // Re-add the excluded Tags
196
        $content = str_replace( array_keys( $excluded ), array_values( $excluded ), $content );
197
198
        return $content;
199
    }
200
}
201