1: <?php
2:
3: namespace Alpha\Util\Extension;
4:
5: use Alpha\Util\Config\ConfigProvider;
6: use Alpha\View\Widget\Image;
7: use Alpha\Exception\AlphaException;
8:
9: /**
10: * A facade class for the Markdown library.
11: *
12: * @since 1.0
13: *
14: * @author John Collins <dev@alphaframework.org>
15: * @license http://www.opensource.org/licenses/bsd-license.php The BSD License
16: * @copyright Copyright (c) 2015, John Collins (founder of Alpha Framework).
17: * All rights reserved.
18: *
19: * <pre>
20: * Redistribution and use in source and binary forms, with or
21: * without modification, are permitted provided that the
22: * following conditions are met:
23: *
24: * * Redistributions of source code must retain the above
25: * copyright notice, this list of conditions and the
26: * following disclaimer.
27: * * Redistributions in binary form must reproduce the above
28: * copyright notice, this list of conditions and the
29: * following disclaimer in the documentation and/or other
30: * materials provided with the distribution.
31: * * Neither the name of the Alpha Framework nor the names
32: * of its contributors may be used to endorse or promote
33: * products derived from this software without specific
34: * prior written permission.
35: *
36: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
37: * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
38: * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
39: * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40: * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
41: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
42: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
43: * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
44: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
45: * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
46: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
47: * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
48: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
49: * </pre>
50: */
51: class MarkdownFacade
52: {
53: /**
54: * The markdown-format content that we will render.
55: *
56: * @var string
57: *
58: * @since 1.0
59: */
60: private $content;
61:
62: /**
63: * The business object that stores the content will be rendered to Markdown.
64: *
65: * @var Alpha\Model\ActiveRecord
66: *
67: * @since 1.0
68: */
69: private $BO = null;
70:
71: /**
72: * The auto-generated name of the Markdown HTML cache file for the BO.
73: *
74: * @var string
75: *
76: * @since 1.0
77: */
78: private $filename;
79:
80: /**
81: * The constructor.
82: *
83: * @param Alpha\Model\ActiveRecord $BO
84: * @param bool $useCache
85: *
86: * @since 1.0
87: */
88: public function __construct($BO, $useCache = true)
89: {
90: $config = ConfigProvider::getInstance();
91:
92: $this->BO = $BO;
93:
94: if ($this->BO instanceof \Alpha\Model\Article && $this->BO->isLoadedFromFile()) {
95: $underscoreTimeStamp = str_replace(array('-', ' ', ':'), '_', $this->BO->getContentFileDate());
96: $this->filename = $config->get('app.file.store.dir').'cache/html/'.get_class($this->BO).'_'.$this->BO->get('title').'_'.$underscoreTimeStamp.'.html';
97: } else {
98: $this->filename = $config->get('app.file.store.dir').'cache/html/'.get_class($this->BO).'_'.$this->BO->getID().'_'.$this->BO->getVersion().'.html';
99: }
100:
101: if (!$useCache) {
102: $this->content = $this->markdown($this->BO->get('content', true));
103: } else {
104: if ($this->checkCache()) {
105: $this->loadCache();
106: } else {
107: if ($this->BO->get('content', true) == '') {
108: // the content may not be loaded from the DB at this stage due to a previous soft-load
109: $this->BO->reload();
110: }
111:
112: $this->content = $this->markdown($this->BO->get('content', true));
113:
114: $this->cache();
115: }
116: }
117:
118: // Replace all instances of $attachURL in link tags to links to the ViewAttachment controller
119: $attachments = array();
120: preg_match_all('/href\=\"\$attachURL\/.*\"/', $this->content, $attachments);
121:
122: foreach ($attachments[0] as $attachmentURL) {
123: $start = mb_strpos($attachmentURL, '/');
124: $end = mb_strrpos($attachmentURL, '"');
125: $fileName = mb_substr($attachmentURL, $start + 1, $end - ($start + 1));
126:
127: if (method_exists($this->BO, 'getAttachmentSecureURL')) {
128: $this->content = str_replace($attachmentURL, 'href="'.$this->BO->getAttachmentSecureURL($fileName).'" rel="nofollow"', $this->content);
129: }
130: }
131:
132: // Handle image attachments
133: $attachments = array();
134: preg_match_all('/\<img\ src\=\"\$attachURL\/.*\.[a-zA-Z]{3}\"[^<]*/', $this->content, $attachments);
135:
136: foreach ($attachments[0] as $attachmentURL) {
137: preg_match('/\/.*\.[a-zA-Z]{3}/', $attachmentURL, $matches);
138: $fileName = $matches[0];
139:
140: if ($config->get('cms.images.widget')) {
141: // get the details of the source image
142: $path = $this->BO->getAttachmentsLocation().$fileName;
143: $image_details = getimagesize($path);
144: $imgType = $image_details[2];
145: if ($imgType == 1) {
146: $type = 'gif';
147: } elseif ($imgType == 2) {
148: $type = 'jpg';
149: } elseif ($imgType == 3) {
150: $type = 'png';
151: }
152:
153: $img = new Image($path, $image_details[0], $image_details[1], $type, 0.95, false, (boolean) $config->get('cms.images.widget.secure'));
154:
155: $this->content = str_replace($attachmentURL, $img->renderHTMLLink(), $this->content);
156: } else {
157: // render a normal image link to the ViewAttachment controller
158: if (method_exists($this->BO, 'getAttachmentSecureURL')) {
159: $this->content = str_replace($attachmentURL, '<img src="'.$this->BO->getAttachmentSecureURL($fileName).'">', $this->content);
160: }
161: }
162: }
163: }
164:
165: /**
166: * Facade method which will invoke our custom markdown class rather than the standard one.
167: *
168: * @return string
169: *
170: * @since 1.0
171: */
172: public function markdown($text)
173: {
174: $config = ConfigProvider::getInstance();
175:
176: // Initialize the parser and return the result of its transform method.
177: static $parser;
178:
179: if (!isset($parser)) {
180: $parser = new \Alpha\Util\Extension\Markdown();
181: }
182:
183: /*
184: * Replace all instances of $sysURL in the text with the app.url setting from config
185: */
186: $text = str_replace('$sysURL', $config->get('app.url'), $text);
187:
188: // transform text using parser.
189: return $parser->transform($text);
190: }
191:
192: /**
193: * Getter for the content.
194: *
195: * @return string
196: *
197: * @since 1.0
198: */
199: public function getContent()
200: {
201: return $this->content;
202: }
203:
204: /**
205: * Saves the HTML generated by Markdown to the cache directory.
206: *
207: * @throws Alpha\Exception\AlphaException
208: *
209: * @since 1.0
210: */
211: private function cache()
212: {
213: // check to ensure that the article is not transient before caching it
214: if (!$this->BO->isTransient() && $this->filename != '') {
215: $fp = fopen($this->filename, 'w');
216: if (!$fp) {
217: throw new AlphaException('Failed to open the cache file for writing, directory permissions my not be set correctly!');
218: } else {
219: flock($fp, 2); // locks the file for writting
220: fwrite($fp, $this->content);
221: flock($fp, 3); // unlocks the file
222: fclose($fp); //closes the file
223: }
224: }
225: }
226:
227: /**
228: * Used to check the HTML cache for the BO cache file.
229: *
230: * @return bool
231: *
232: * @since 1.0
233: */
234: public function checkCache()
235: {
236: return file_exists($this->filename);
237: }
238:
239: /**
240: * Method to load the content of the cache file to the $content attribute of this object.
241: *
242: * @throws Alpha\Exception\AlphaException
243: *
244: * @since 1.0
245: */
246: public function loadCache()
247: {
248: $fp = fopen($this->filename, 'r');
249:
250: if (!$fp) {
251: throw new AlphaException('Failed to open the cache file for reading, directory permissions my not be set correctly!');
252: } else {
253: $this->content = fread($fp, filesize($this->filename));
254: fclose($fp); //closes the file
255: }
256: }
257: }
258: