Completed
Push — develop ( 61b686...02f872 )
by John
03:10
created

MarkdownFacade::cache()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 15
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 15
rs 9.2
cc 4
eloc 10
nc 3
nop 0
1
<?php
2
3
namespace Alpha\Util\Extension;
4
5
use Alpha\Util\Config\ConfigProvider;
6
use Alpha\Util\Service\ServiceFactory;
7
use Alpha\View\Widget\Image;
8
use Alpha\Exception\AlphaException;
9
10
/**
11
 * A facade class for the Markdown library.
12
 *
13
 * @since 1.0
14
 *
15
 * @author John Collins <[email protected]>
16
 * @license http://www.opensource.org/licenses/bsd-license.php The BSD License
17
 * @copyright Copyright (c) 2017, John Collins (founder of Alpha Framework).
18
 * All rights reserved.
19
 *
20
 * <pre>
21
 * Redistribution and use in source and binary forms, with or
22
 * without modification, are permitted provided that the
23
 * following conditions are met:
24
 *
25
 * * Redistributions of source code must retain the above
26
 *   copyright notice, this list of conditions and the
27
 *   following disclaimer.
28
 * * Redistributions in binary form must reproduce the above
29
 *   copyright notice, this list of conditions and the
30
 *   following disclaimer in the documentation and/or other
31
 *   materials provided with the distribution.
32
 * * Neither the name of the Alpha Framework nor the names
33
 *   of its contributors may be used to endorse or promote
34
 *   products derived from this software without specific
35
 *   prior written permission.
36
 *
37
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
38
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
39
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
40
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
41
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
42
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
43
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
44
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
45
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
46
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
47
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
48
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
49
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
50
 * </pre>
51
 */
52
class MarkdownFacade
53
{
54
    /**
55
     * The markdown-format content that we will render.
56
     *
57
     * @var string
58
     *
59
     * @since 1.0
60
     */
61
    private $content;
62
63
    /**
64
     * The business object that stores the content will be rendered to Markdown.
65
     *
66
     * @var \Alpha\Model\ActiveRecord
67
     *
68
     * @since 1.0
69
     */
70
    private $record = null;
71
72
    /**
73
     * The auto-generated name of the cache key.
74
     *
75
     * @var string
76
     *
77
     * @since 1.0
78
     */
79
    private $cacheKey;
80
81
    /**
82
     * The constructor.
83
     *
84
     * @param \Alpha\Model\ActiveRecord $record
85
     * @param bool                     $useCache
86
     *
87
     * @since 1.0
88
     */
89
    public function __construct($record, $useCache = true)
90
    {
91
        $config = ConfigProvider::getInstance();
92
        $cache = ServiceFactory::getInstance($config->get('cache.provider.name'), 'Alpha\Util\Cache\CacheProviderInterface');
93
94
        $this->record = $record;
95
96
        if ($this->record instanceof \Alpha\Model\Article && $this->record->isLoadedFromFile()) {
97
            $underscoreTimeStamp = str_replace(array('-', ' ', ':'), '_', $this->record->getContentFileDate());
98
            $this->cacheKey = 'html_'.get_class($this->record).'_'.$this->record->get('title').'_'.$underscoreTimeStamp;
99
        } else {
100
            $this->cacheKey = 'html_'.get_class($this->record).'_'.$this->record->getID().'_'.$this->record->getVersion();
101
        }
102
103
        if (!$useCache) {
104
            $this->content = $this->markdown($this->record->get('content', true));
105
        } else {
106
            if ($cache->get($this->cacheKey) !== false) {
107
                $this->content = $cache->get($this->cacheKey);
108
            } else {
109
                if ($this->record->get('content', true) == '') {
110
                    // the content may not be loaded from the DB at this stage due to a previous soft-load
111
                    $this->record->reload();
112
                }
113
114
                $this->content = $this->markdown($this->record->get('content', true));
115
116
                $cache->set($this->cacheKey, $this->content);
117
            }
118
        }
119
120
        // Replace all instances of $attachURL in link tags to links to the ViewAttachment controller
121
        $attachments = array();
122
        preg_match_all('/href\=\"\$attachURL\/.*\"/', $this->content, $attachments);
123
124
        foreach ($attachments[0] as $attachmentURL) {
125
            $start = mb_strpos($attachmentURL, '/');
126
            $end = mb_strrpos($attachmentURL, '"');
127
            $fileName = mb_substr($attachmentURL, $start+1, $end-($start+1));
128
129
            if (method_exists($this->record, 'getAttachmentSecureURL')) {
130
                $this->content = str_replace($attachmentURL, 'href="'.$this->record->getAttachmentSecureURL($fileName).'" rel="nofollow"', $this->content);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Alpha\Model\ActiveRecord as the method getAttachmentSecureURL() does only exist in the following sub-classes of Alpha\Model\ActiveRecord: Alpha\Model\Article. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
131
            }
132
        }
133
134
        // Handle image attachments
135
        $attachments = array();
136
        preg_match_all('/\<img\ src\=\"\$attachURL\/.*\.[a-zA-Z]{3}\"[^<]*/', $this->content, $attachments);
137
138
        foreach ($attachments[0] as $attachmentURL) {
139
            preg_match('/\/.*\.[a-zA-Z]{3}/', $attachmentURL, $matches);
140
            $fileName = $matches[0];
141
142
            if ($config->get('cms.images.widget')) {
143
                // get the details of the source image
144
                $path = $this->record->getAttachmentsLocation().$fileName;
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Alpha\Model\ActiveRecord as the method getAttachmentsLocation() does only exist in the following sub-classes of Alpha\Model\ActiveRecord: Alpha\Model\Article. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
145
                $image_details = getimagesize($path);
146
                $imgType = $image_details[2];
147
                if ($imgType == 1) {
148
                    $type = 'gif';
149
                } elseif ($imgType == 2) {
150
                    $type = 'jpg';
151
                } else {
152
                    $type = 'png';
153
                }
154
155
                $img = new Image($path, $image_details[0], $image_details[1], $type, 0.95, false, (boolean)$config->get('cms.images.widget.secure'));
156
157
                $this->content = str_replace($attachmentURL, $img->renderHTMLLink(), $this->content);
158
            } else {
159
                // render a normal image link to the ViewAttachment controller
160
                if (method_exists($this->record, 'getAttachmentSecureURL')) {
161
                    $this->content = str_replace($attachmentURL, '<img src="'.$this->record->getAttachmentSecureURL($fileName).'">', $this->content);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Alpha\Model\ActiveRecord as the method getAttachmentSecureURL() does only exist in the following sub-classes of Alpha\Model\ActiveRecord: Alpha\Model\Article. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
162
                }
163
            }
164
        }
165
    }
166
167
    /**
168
     * Facade method which will invoke our custom markdown class rather than the standard one.
169
     *
170
     * @return string
171
     *
172
     * @since 1.0
173
     */
174
    public function markdown($text)
175
    {
176
        $config = ConfigProvider::getInstance();
177
178
        // Initialize the parser and return the result of its transform method.
179
        static $parser;
180
181
        if (!isset($parser)) {
182
            $parser = new \Alpha\Util\Extension\Markdown();
183
        }
184
185
        /*
186
         * Replace all instances of $sysURL in the text with the app.url setting from config
187
         */
188
        $text = str_replace('$sysURL', $config->get('app.url'), $text);
189
190
        // transform text using parser.
191
        return $parser->transform($text);
192
    }
193
194
    /**
195
     * Getter for the content.
196
     *
197
     * @return string
198
     *
199
     * @since 1.0
200
     */
201
    public function getContent()
202
    {
203
        return $this->content;
204
    }
205
}
206