xref: /plugin/combo/syntax/frontmatter.php (revision aa3cb38f16c718b4f409afba33b2d5efdbe0760a)
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 metadat
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                // Canonical should be lowercase
166                if ($key == Page::CANONICAL_PROPERTY) {
167                    $result[Page::CANONICAL_PROPERTY] = $value;
168                    $value = strtolower($value);
169                }
170
171                // Set the value persistently
172                p_set_metadata($ID, array($key => $value));
173
174            }
175
176            $this->closeParsing($json);
177
178            $result["state"]= self::PARSING_STATE_SUCCESSFUL;
179
180            return $result;
181        }
182
183        return array();
184    }
185
186    /**
187     * Render the output
188     * @param string $format
189     * @param Doku_Renderer $renderer
190     * @param array $data - what the function handle() return'ed
191     * @return boolean - rendered correctly? (however, returned value is not used at the moment)
192     * @see DokuWiki_Syntax_Plugin::render()
193     *
194     *
195     */
196    function render($format, Doku_Renderer $renderer, $data)
197    {
198        // TODO: https://developers.google.com/search/docs/data-types/breadcrumb#breadcrumb-list
199        // News article: https://developers.google.com/search/docs/data-types/article
200        // News article: https://developers.google.com/search/docs/data-types/paywalled-content
201        // What is ?: https://developers.google.com/search/docs/data-types/qapage
202        // How to ?: https://developers.google.com/search/docs/data-types/how-to
203
204        switch ($format) {
205            case 'xhtml':
206                global $ID;
207                /** @var Doku_Renderer_xhtml $renderer */
208                $state = $data["state"];
209                if ($state == self::PARSING_STATE_ERROR) {
210                    LogUtility::msg("Front Matter: The json object for the page ($ID) is not valid", LogUtility::LVL_MSG_ERROR);
211                }
212                break;
213            case Analytics::RENDERER_FORMAT:
214                /** @var renderer_plugin_combo_analytics $renderer */
215                if (array_key_exists("description", $data)) {
216                    $renderer->setMeta("description", $data["description"]);
217                }
218                if (array_key_exists(Page::CANONICAL_PROPERTY, $data)) {
219                    $renderer->setMeta(Page::CANONICAL_PROPERTY, $data[Page::CANONICAL_PROPERTY]);
220                }
221                if (array_key_exists(Page::TITLE_PROPERTY, $data)) {
222                    $renderer->setMeta(Page::TITLE_PROPERTY, $data[Page::TITLE_PROPERTY]);
223                }
224                break;
225
226        }
227        return true;
228    }
229
230    /**
231     *
232     * @param array $json - The Json
233     * Delete the controlled meta that are no more present if they exists
234     * @return bool
235     */
236    public function closeParsing(array $json = array())
237    {
238        global $ID;
239
240        /**
241         * The managed meta with the exception of
242         * the {@link action_plugin_combo_metadescription::DESCRIPTION_META_KEY description}
243         * because it's already managed by dokuwiki in description['abstract']
244         */
245        $managedMeta = [
246            Page::CANONICAL_PROPERTY,
247            action_plugin_combo_metatitle::TITLE_META_KEY,
248            syntax_plugin_combo_disqus::META_DISQUS_IDENTIFIER
249        ];
250        $meta = p_read_metadata($ID);
251        foreach ($managedMeta as $metaKey) {
252            if (!array_key_exists($metaKey, $json)) {
253                if (isset($meta['persistent'][$metaKey])) {
254                    unset($meta['persistent'][$metaKey]);
255                }
256            }
257        }
258        return p_save_metadata($ID, $meta);
259    }
260
261
262}
263
264