xref: /plugin/combo/syntax/frontmatter.php (revision 9b9e6d1fd4c31e9d70c96490a12e4d7445d392eb)
1<?php
2/**
3 * Front Matter implementation to add metadata
4 *
5 *
6 * that enhance the metadata dokuwiki system
7 * https://www.dokuwiki.org/metadata
8 * that use the Dublin Core Standard
9 * http://dublincore.org/
10 * by adding the front matter markup specification
11 * https://gerardnico.com/markup/front-matter
12 *
13 * Inspiration
14 * https://github.com/dokufreaks/plugin-meta/blob/master/syntax.php
15 * https://www.dokuwiki.org/plugin:semantic
16 *
17 * See also structured plugin
18 * https://www.dokuwiki.org/plugin:data
19 * https://www.dokuwiki.org/plugin:struct
20 *
21 */
22
23use ComboStrap\Analytics;
24use ComboStrap\LogUtility;
25use ComboStrap\PluginUtility;
26use ComboStrap\Page;
27
28require_once(__DIR__ . '/../class/Analytics.php');
29
30if (!defined('DOKU_INC')) {
31    die();
32}
33
34/**
35 * All DokuWiki plugins to extend the parser/rendering mechanism
36 * need to inherit from this class
37 */
38class syntax_plugin_combo_frontmatter extends DokuWiki_Syntax_Plugin
39{
40    const PARSING_STATE_EMPTY = "empty";
41    const PARSING_STATE_ERROR = "error";
42    const PARSING_STATE_SUCCESSFUL = "successful";
43
44    /**
45     * Syntax Type.
46     *
47     * Needs to return one of the mode types defined in $PARSER_MODES in parser.php
48     * @see https://www.dokuwiki.org/devel:syntax_plugins#syntax_types
49     *
50     * Return an array of one or more of the mode types {@link $PARSER_MODES} in Parser.php
51     *
52     * baseonly - run only in the base
53     */
54    function getType()
55    {
56        return 'baseonly';
57    }
58
59    /**
60     * @see Doku_Parser_Mode::getSort()
61     * Higher number than the teaser-columns
62     * because the mode with the lowest sort number will win out
63     */
64    function getSort()
65    {
66        return 99;
67    }
68
69    /**
70     * Create a pattern that will called this plugin
71     *
72     * @param string $mode
73     * @see Doku_Parser_Mode::connectTo()
74     */
75    function connectTo($mode)
76    {
77        if ($mode == "base") {
78            // only from the top
79            $this->Lexer->addSpecialPattern('---json.*?---', $mode, PluginUtility::getModeForComponent($this->getPluginComponent()));
80        }
81    }
82
83    /**
84     *
85     * The handle function goal is to parse the matched syntax through the pattern function
86     * and to return the result for use in the renderer
87     * This result is always cached until the page is modified.
88     * @param string $match
89     * @param int $state
90     * @param int $pos
91     * @param Doku_Handler $handler
92     * @return array|bool
93     * @see DokuWiki_Syntax_Plugin::handle()
94     *
95     */
96    function handle($match, $state, $pos, Doku_Handler $handler)
97    {
98
99        if ($state == DOKU_LEXER_SPECIAL) {
100
101            global $ID;
102
103            // strip
104            //   from start `---json` + eol = 8
105            //   from end   `---` + eol = 4
106            $match = substr($match, 7, -3);
107
108            // Empty front matter
109            if (trim($match) == "") {
110                $this->closeParsing();
111                return array("state" => self::PARSING_STATE_EMPTY);
112            }
113
114            // Otherwise you get an object ie $arrayFormat-> syntax
115            $arrayFormat = true;
116            $json = json_decode($match, $arrayFormat);
117
118            // Decodage problem
119            if ($json == null) {
120                return array("state" => self::PARSING_STATE_ERROR);
121            }
122
123            // Trim it
124            $jsonKey = array_map('trim', array_keys($json));
125            // We will get a php warning here because the values may be an array
126            // and trim accept only string
127            $oldLevel = error_reporting(E_ERROR);
128            $jsonValues = array_map('trim', $json);
129            error_reporting($oldLevel);
130            $json = array_combine($jsonKey, $jsonValues);
131
132
133            $notModifiableMeta = [
134                "date",
135                "user",
136                "last_change",
137                "creator",
138                "contributor"
139            ];
140            $result = array();
141            foreach ($json as $key => $value) {
142
143                // Not modifiable metadata
144                if (in_array($key, $notModifiableMeta)) {
145                    LogUtility::msg("Front Matter: The metadata ($key) is a protected metadata and cannot be modified", LogUtility::LVL_MSG_WARNING);
146                    continue;
147                }
148
149                // Description is special
150                if ($key == "description") {
151                    $result["description"] = $value;
152                    p_set_metadata($ID, array("description" => array("abstract" => $value)));
153                    continue;
154                }
155
156                /**
157                 * Pass the title to the metadata
158                 * to advertise that it's in the front-matter
159                 * for the quality rules
160                 */
161                if ($key == Page::TITLE_PROPERTY) {
162                    $result[Page::TITLE_PROPERTY] = $value;
163                }
164
165                /**
166                 * Pass the low quality indicator
167                 * to advertise that it's in the front-matter
168                 */
169                if ($key == Page::LOW_QUALITY_PAGE_INDICATOR) {
170                    $result[Page::LOW_QUALITY_PAGE_INDICATOR] = $value;
171                }
172
173                // Canonical should be lowercase
174                if ($key == Page::CANONICAL_PROPERTY) {
175                    $result[Page::CANONICAL_PROPERTY] = $value;
176                    $value = strtolower($value);
177                }
178
179                // Set the value persistently
180                p_set_metadata($ID, array($key => $value));
181
182            }
183
184            $this->closeParsing($json);
185
186            $result["state"]= self::PARSING_STATE_SUCCESSFUL;
187
188            return $result;
189        }
190
191        return array();
192    }
193
194    /**
195     * Render the output
196     * @param string $format
197     * @param Doku_Renderer $renderer
198     * @param array $data - what the function handle() return'ed
199     * @return boolean - rendered correctly? (however, returned value is not used at the moment)
200     * @see DokuWiki_Syntax_Plugin::render()
201     *
202     *
203     */
204    function render($format, Doku_Renderer $renderer, $data)
205    {
206        // TODO: https://developers.google.com/search/docs/data-types/breadcrumb#breadcrumb-list
207        // News article: https://developers.google.com/search/docs/data-types/article
208        // News article: https://developers.google.com/search/docs/data-types/paywalled-content
209        // What is ?: https://developers.google.com/search/docs/data-types/qapage
210        // How to ?: https://developers.google.com/search/docs/data-types/how-to
211
212        switch ($format) {
213            case 'xhtml':
214                global $ID;
215                /** @var Doku_Renderer_xhtml $renderer */
216                $state = $data["state"];
217                if ($state == self::PARSING_STATE_ERROR) {
218                    LogUtility::msg("Front Matter: The json object for the page ($ID) is not valid", LogUtility::LVL_MSG_ERROR);
219                }
220                break;
221            case Analytics::RENDERER_FORMAT:
222                /** @var renderer_plugin_combo_analytics $renderer */
223                if (array_key_exists("description", $data)) {
224                    $renderer->setMeta("description", $data["description"]);
225                }
226                if (array_key_exists(Page::CANONICAL_PROPERTY, $data)) {
227                    $renderer->setMeta(Page::CANONICAL_PROPERTY, $data[Page::CANONICAL_PROPERTY]);
228                }
229                if (array_key_exists(Page::TITLE_PROPERTY, $data)) {
230                    $renderer->setMeta(Page::TITLE_PROPERTY, $data[Page::TITLE_PROPERTY]);
231                }
232                if (array_key_exists(Page::LOW_QUALITY_PAGE_INDICATOR, $data)) {
233                    $renderer->setMeta(Page::LOW_QUALITY_PAGE_INDICATOR, $data[Page::LOW_QUALITY_PAGE_INDICATOR]);
234                }
235                break;
236
237        }
238        return true;
239    }
240
241    /**
242     *
243     * @param array $json - The Json
244     * Delete the controlled meta that are no more present if they exists
245     * @return bool
246     */
247    public function closeParsing(array $json = array())
248    {
249        global $ID;
250
251        /**
252         * The managed meta with the exception of
253         * the {@link action_plugin_combo_metadescription::DESCRIPTION_META_KEY description}
254         * because it's already managed by dokuwiki in description['abstract']
255         */
256        $managedMeta = [
257            Page::CANONICAL_PROPERTY,
258            action_plugin_combo_metatitle::TITLE_META_KEY,
259            syntax_plugin_combo_disqus::META_DISQUS_IDENTIFIER
260        ];
261        $meta = p_read_metadata($ID);
262        foreach ($managedMeta as $metaKey) {
263            if (!array_key_exists($metaKey, $json)) {
264                if (isset($meta['persistent'][$metaKey])) {
265                    unset($meta['persistent'][$metaKey]);
266                }
267            }
268        }
269        return p_save_metadata($ID, $meta);
270    }
271
272
273}
274
275