xref: /plugin/combo/syntax/frontmatter.php (revision 71f916b90657b0ec64f620dd9cb434dd66a4bb62)
1007225e5Sgerardnico<?php
2007225e5Sgerardnico/**
3007225e5Sgerardnico * Front Matter implementation to add metadata
4007225e5Sgerardnico *
5007225e5Sgerardnico *
6007225e5Sgerardnico * that enhance the metadata dokuwiki system
7007225e5Sgerardnico * https://www.dokuwiki.org/metadata
8007225e5Sgerardnico * that use the Dublin Core Standard
9007225e5Sgerardnico * http://dublincore.org/
10007225e5Sgerardnico * by adding the front matter markup specification
11007225e5Sgerardnico * https://gerardnico.com/markup/front-matter
12007225e5Sgerardnico *
13007225e5Sgerardnico * Inspiration
14007225e5Sgerardnico * https://github.com/dokufreaks/plugin-meta/blob/master/syntax.php
15007225e5Sgerardnico * https://www.dokuwiki.org/plugin:semantic
16007225e5Sgerardnico *
17007225e5Sgerardnico * See also structured plugin
18007225e5Sgerardnico * https://www.dokuwiki.org/plugin:data
19007225e5Sgerardnico * https://www.dokuwiki.org/plugin:struct
20007225e5Sgerardnico *
21007225e5Sgerardnico */
22007225e5Sgerardnico
23007225e5Sgerardnicouse ComboStrap\Analytics;
24007225e5Sgerardnicouse ComboStrap\LogUtility;
25007225e5Sgerardnicouse ComboStrap\PluginUtility;
26*71f916b9Sgerardnicouse ComboStrap\Page;
27007225e5Sgerardnico
28007225e5Sgerardnicorequire_once(__DIR__ . '/../class/Analytics.php');
29007225e5Sgerardnico
30007225e5Sgerardnicoif (!defined('DOKU_INC')) {
31007225e5Sgerardnico    die();
32007225e5Sgerardnico}
33007225e5Sgerardnico
34007225e5Sgerardnico/**
35007225e5Sgerardnico * All DokuWiki plugins to extend the parser/rendering mechanism
36007225e5Sgerardnico * need to inherit from this class
37007225e5Sgerardnico */
38007225e5Sgerardnicoclass syntax_plugin_combo_frontmatter extends DokuWiki_Syntax_Plugin
39007225e5Sgerardnico{
40007225e5Sgerardnico    const PARSING_STATE_EMPTY = "empty";
41007225e5Sgerardnico    const PARSING_STATE_ERROR = "error";
42007225e5Sgerardnico    const PARSING_STATE_SUCCESSFUL = "successful";
43007225e5Sgerardnico
44007225e5Sgerardnico    /**
45007225e5Sgerardnico     * Syntax Type.
46007225e5Sgerardnico     *
47007225e5Sgerardnico     * Needs to return one of the mode types defined in $PARSER_MODES in parser.php
48007225e5Sgerardnico     * @see https://www.dokuwiki.org/devel:syntax_plugins#syntax_types
49007225e5Sgerardnico     *
50007225e5Sgerardnico     * Return an array of one or more of the mode types {@link $PARSER_MODES} in Parser.php
51007225e5Sgerardnico     *
52007225e5Sgerardnico     * baseonly - run only in the base
53007225e5Sgerardnico     */
54007225e5Sgerardnico    function getType()
55007225e5Sgerardnico    {
56007225e5Sgerardnico        return 'baseonly';
57007225e5Sgerardnico    }
58007225e5Sgerardnico
59007225e5Sgerardnico    /**
60007225e5Sgerardnico     * @see Doku_Parser_Mode::getSort()
61007225e5Sgerardnico     * Higher number than the teaser-columns
62007225e5Sgerardnico     * because the mode with the lowest sort number will win out
63007225e5Sgerardnico     */
64007225e5Sgerardnico    function getSort()
65007225e5Sgerardnico    {
66007225e5Sgerardnico        return 99;
67007225e5Sgerardnico    }
68007225e5Sgerardnico
69007225e5Sgerardnico    /**
70007225e5Sgerardnico     * Create a pattern that will called this plugin
71007225e5Sgerardnico     *
72007225e5Sgerardnico     * @param string $mode
73007225e5Sgerardnico     * @see Doku_Parser_Mode::connectTo()
74007225e5Sgerardnico     */
75007225e5Sgerardnico    function connectTo($mode)
76007225e5Sgerardnico    {
77007225e5Sgerardnico        if ($mode == "base") {
78007225e5Sgerardnico            // only from the top
79007225e5Sgerardnico            $this->Lexer->addSpecialPattern('---json.*?---', $mode, PluginUtility::getModeForComponent($this->getPluginComponent()));
80007225e5Sgerardnico        }
81007225e5Sgerardnico    }
82007225e5Sgerardnico
83007225e5Sgerardnico    /**
84007225e5Sgerardnico     *
85007225e5Sgerardnico     * The handle function goal is to parse the matched syntax through the pattern function
86007225e5Sgerardnico     * and to return the result for use in the renderer
87007225e5Sgerardnico     * This result is always cached until the page is modified.
88007225e5Sgerardnico     * @param string $match
89007225e5Sgerardnico     * @param int $state
90007225e5Sgerardnico     * @param int $pos
91007225e5Sgerardnico     * @param Doku_Handler $handler
92007225e5Sgerardnico     * @return array|bool
93007225e5Sgerardnico     * @see DokuWiki_Syntax_Plugin::handle()
94007225e5Sgerardnico     *
95007225e5Sgerardnico     */
96007225e5Sgerardnico    function handle($match, $state, $pos, Doku_Handler $handler)
97007225e5Sgerardnico    {
98007225e5Sgerardnico
99007225e5Sgerardnico        if ($state == DOKU_LEXER_SPECIAL) {
100007225e5Sgerardnico
101007225e5Sgerardnico            global $ID;
102007225e5Sgerardnico
103007225e5Sgerardnico            // strip
104007225e5Sgerardnico            //   from start `---json` + eol = 8
105007225e5Sgerardnico            //   from end   `---` + eol = 4
106007225e5Sgerardnico            $match = substr($match, 7, -3);
107007225e5Sgerardnico
108007225e5Sgerardnico            // Empty front matter
109007225e5Sgerardnico            if (trim($match) == "") {
110007225e5Sgerardnico                $this->closeParsing();
111007225e5Sgerardnico                return array("state" => self::PARSING_STATE_EMPTY);
112007225e5Sgerardnico            }
113007225e5Sgerardnico
114007225e5Sgerardnico            // Otherwise you get an object ie $arrayFormat-> syntax
115007225e5Sgerardnico            $arrayFormat = true;
116007225e5Sgerardnico            $json = json_decode($match, $arrayFormat);
117007225e5Sgerardnico
118007225e5Sgerardnico            // Decodage problem
119007225e5Sgerardnico            if ($json == null) {
120007225e5Sgerardnico                return array("state" => self::PARSING_STATE_ERROR);
121007225e5Sgerardnico            }
122007225e5Sgerardnico
123007225e5Sgerardnico            // Trim it
124007225e5Sgerardnico            $jsonKey = array_map('trim', array_keys($json));
125007225e5Sgerardnico            // We will get a php warning here because the values may be an array
126007225e5Sgerardnico            // and trim accept only string
127007225e5Sgerardnico            $oldLevel = error_reporting(E_ERROR);
128007225e5Sgerardnico            $jsonValues = array_map('trim', $json);
129007225e5Sgerardnico            error_reporting($oldLevel);
130007225e5Sgerardnico            $json = array_combine($jsonKey, $jsonValues);
131007225e5Sgerardnico
132007225e5Sgerardnico
133007225e5Sgerardnico            $notModifiableMeta = [
134007225e5Sgerardnico                "date",
135007225e5Sgerardnico                "user",
136007225e5Sgerardnico                "last_change",
137007225e5Sgerardnico                "creator",
138007225e5Sgerardnico                "contributor"
139007225e5Sgerardnico            ];
140007225e5Sgerardnico            $result = array();
141007225e5Sgerardnico            foreach ($json as $key => $value) {
142007225e5Sgerardnico
143007225e5Sgerardnico                // Not modifiable metadata
144007225e5Sgerardnico                if (in_array($key, $notModifiableMeta)) {
145007225e5Sgerardnico                    LogUtility::msg("Front Matter: The metadata ($key) is a protected metadata and cannot be modified", LogUtility::LVL_MSG_WARNING);
146007225e5Sgerardnico                    continue;
147007225e5Sgerardnico                }
148007225e5Sgerardnico
149007225e5Sgerardnico                // Description is special
150007225e5Sgerardnico                if ($key == "description") {
151007225e5Sgerardnico                    $result["description"] = $value;
152007225e5Sgerardnico                    p_set_metadata($ID, array("description" => array("abstract" => $value)));
153007225e5Sgerardnico                    continue;
154007225e5Sgerardnico                }
155007225e5Sgerardnico
156007225e5Sgerardnico                // Canonical should be lowercase
157*71f916b9Sgerardnico                if ($key == Page::CANONICAL_PROPERTY) {
158*71f916b9Sgerardnico                    $result[Page::CANONICAL_PROPERTY] = $value;
159007225e5Sgerardnico                    $value = strtolower($value);
160007225e5Sgerardnico                }
161007225e5Sgerardnico
162007225e5Sgerardnico                // Set the value persistently
163007225e5Sgerardnico                p_set_metadata($ID, array($key => $value));
164007225e5Sgerardnico
165007225e5Sgerardnico            }
166007225e5Sgerardnico
167007225e5Sgerardnico            $this->closeParsing($json);
168007225e5Sgerardnico
169007225e5Sgerardnico            $result["state"]= self::PARSING_STATE_SUCCESSFUL;
170007225e5Sgerardnico
171007225e5Sgerardnico            return $result;
172007225e5Sgerardnico        }
173007225e5Sgerardnico
174007225e5Sgerardnico        return array();
175007225e5Sgerardnico    }
176007225e5Sgerardnico
177007225e5Sgerardnico    /**
178007225e5Sgerardnico     * Render the output
179007225e5Sgerardnico     * @param string $format
180007225e5Sgerardnico     * @param Doku_Renderer $renderer
181007225e5Sgerardnico     * @param array $data - what the function handle() return'ed
182007225e5Sgerardnico     * @return boolean - rendered correctly? (however, returned value is not used at the moment)
183007225e5Sgerardnico     * @see DokuWiki_Syntax_Plugin::render()
184007225e5Sgerardnico     *
185007225e5Sgerardnico     *
186007225e5Sgerardnico     */
187007225e5Sgerardnico    function render($format, Doku_Renderer $renderer, $data)
188007225e5Sgerardnico    {
189007225e5Sgerardnico        // TODO: https://developers.google.com/search/docs/data-types/breadcrumb#breadcrumb-list
190007225e5Sgerardnico        // News article: https://developers.google.com/search/docs/data-types/article
191007225e5Sgerardnico        // News article: https://developers.google.com/search/docs/data-types/paywalled-content
192007225e5Sgerardnico        // What is ?: https://developers.google.com/search/docs/data-types/qapage
193007225e5Sgerardnico        // How to ?: https://developers.google.com/search/docs/data-types/how-to
194007225e5Sgerardnico
195007225e5Sgerardnico        switch ($format) {
196007225e5Sgerardnico            case 'xhtml':
197007225e5Sgerardnico                global $ID;
198007225e5Sgerardnico                /** @var Doku_Renderer_xhtml $renderer */
199007225e5Sgerardnico                $state = $data["state"];
200007225e5Sgerardnico                if ($state == self::PARSING_STATE_ERROR) {
201007225e5Sgerardnico                    LogUtility::msg("Front Matter: The json object for the page ($ID) is not valid", LogUtility::LVL_MSG_ERROR);
202007225e5Sgerardnico                }
203007225e5Sgerardnico                break;
204007225e5Sgerardnico            case Analytics::RENDERER_FORMAT:
205007225e5Sgerardnico                /** @var renderer_plugin_combo_analytics $renderer */
206007225e5Sgerardnico                if (array_key_exists("description", $data)) {
207007225e5Sgerardnico                    $renderer->setMeta("description", $data["description"]);
208007225e5Sgerardnico                }
209*71f916b9Sgerardnico                if (array_key_exists(Page::CANONICAL_PROPERTY, $data)) {
210*71f916b9Sgerardnico                    $renderer->setMeta(Page::CANONICAL_PROPERTY, $data[Page::CANONICAL_PROPERTY]);
211007225e5Sgerardnico                }
212007225e5Sgerardnico                break;
213007225e5Sgerardnico
214007225e5Sgerardnico        }
215007225e5Sgerardnico        return true;
216007225e5Sgerardnico    }
217007225e5Sgerardnico
218007225e5Sgerardnico    /**
219007225e5Sgerardnico     *
220007225e5Sgerardnico     * @param array $json - The Json
221007225e5Sgerardnico     * Delete the controlled meta that are no more present if they exists
222007225e5Sgerardnico     * @return bool
223007225e5Sgerardnico     */
224007225e5Sgerardnico    public function closeParsing(array $json = array())
225007225e5Sgerardnico    {
226007225e5Sgerardnico        global $ID;
227007225e5Sgerardnico
228007225e5Sgerardnico        /**
229007225e5Sgerardnico         * The managed meta with the exception of
230007225e5Sgerardnico         * the {@link action_plugin_combo_metadescription::DESCRIPTION_META_KEY description}
231007225e5Sgerardnico         * because it's already managed by dokuwiki in description['abstract']
232007225e5Sgerardnico         */
233007225e5Sgerardnico        $managedMeta = [
234*71f916b9Sgerardnico            Page::CANONICAL_PROPERTY,
235007225e5Sgerardnico            action_plugin_combo_metatitle::TITLE_META_KEY,
236007225e5Sgerardnico            syntax_plugin_combo_disqus::META_DISQUS_IDENTIFIER
237007225e5Sgerardnico        ];
238007225e5Sgerardnico        $meta = p_read_metadata($ID);
239007225e5Sgerardnico        foreach ($managedMeta as $metaKey) {
240007225e5Sgerardnico            if (!array_key_exists($metaKey, $json)) {
241007225e5Sgerardnico                if (isset($meta['persistent'][$metaKey])) {
242007225e5Sgerardnico                    unset($meta['persistent'][$metaKey]);
243007225e5Sgerardnico                }
244007225e5Sgerardnico            }
245007225e5Sgerardnico        }
246007225e5Sgerardnico        return p_save_metadata($ID, $meta);
247007225e5Sgerardnico    }
248007225e5Sgerardnico
249007225e5Sgerardnico
250007225e5Sgerardnico}
251007225e5Sgerardnico
252