1<?php 2 3/** 4 * DokuWiki Plugin struct (Helper Component) 5 * 6 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 7 * @author Andreas Gohr, Michael Große <dokuwiki@cosmocode.de> 8 */ 9 10use dokuwiki\Extension\Plugin; 11use dokuwiki\plugin\struct\meta\AccessDataValidator; 12use dokuwiki\plugin\struct\meta\AccessTable; 13use dokuwiki\plugin\struct\meta\Assignments; 14use dokuwiki\plugin\struct\meta\Schema; 15use dokuwiki\plugin\struct\meta\StructException; 16 17/** 18 * The public interface for the struct plugin 19 * 20 * 3rd party developers should always interact with struct data through this 21 * helper plugin only. If additional interface functionality is needed, 22 * it should be added here. 23 * 24 * All functions will throw StructExceptions when something goes wrong. 25 * 26 * Remember to check permissions yourself! 27 */ 28class helper_plugin_struct extends Plugin 29{ 30 /** 31 * Class names of renderers which should NOT render struct data. 32 * All descendants are also blacklisted. 33 */ 34 public const BLACKLIST_RENDERER = [ 35 'Doku_Renderer_metadata', 36 '\renderer_plugin_qc' 37 ]; 38 39 /** 40 * Get the structured data of a given page 41 * 42 * @param string $page The page to get data for 43 * @param string|null $schema The schema to use null for all 44 * @param int $time A timestamp if you want historic data 45 * @return array ('schema' => ( 'fieldlabel' => 'value', ...)) 46 * @throws StructException 47 */ 48 public function getData($page, $schema = null, $time = 0) 49 { 50 $page = cleanID($page); 51 if (!$time) { 52 $time = time(); 53 } 54 55 if (is_null($schema)) { 56 $assignments = Assignments::getInstance(); 57 $schemas = $assignments->getPageAssignments($page, false); 58 } else { 59 $schemas = [$schema]; 60 } 61 62 $result = []; 63 foreach ($schemas as $schema) { 64 $schemaData = AccessTable::getPageAccess($schema, $page, $time); 65 $result[$schema] = $schemaData->getDataArray(); 66 } 67 68 return $result; 69 } 70 71 /** 72 * Saves data for a given page (creates a new revision) 73 * 74 * If this call succeeds you can assume your data has either been saved or it was 75 * not necessary to save it because the data already existed in the wanted form or 76 * the given schemas are no longer assigned to that page. 77 * 78 * Important: You have to check write permissions for the given page before calling 79 * this function yourself! 80 * 81 * this duplicates a bit of code from entry.php - we could also fake post data and let 82 * entry handle it, but that would be rather unclean and might be problematic when multiple 83 * calls are done within the same request. 84 * 85 * @param string $page 86 * @param array $data ('schema' => ( 'fieldlabel' => 'value', ...)) 87 * @param string $summary 88 * @param string $summary 89 * @throws StructException 90 * @todo should this try to lock the page? 91 * 92 * 93 */ 94 public function saveData($page, $data, $summary = '', $minor = false) 95 { 96 $page = cleanID($page); 97 $summary = trim($summary); 98 if (!$summary) $summary = $this->getLang('summary'); 99 100 if (!page_exists($page)) throw new StructException("Page does not exist. You can not attach struct data"); 101 102 // validate and see if anything changes 103 $valid = AccessDataValidator::validateDataForPage($data, $page, $errors); 104 if ($valid === false) { 105 throw new StructException("Validation failed:\n%s", implode("\n", $errors)); 106 } 107 if (!$valid) return; // empty array when no changes were detected 108 109 $newrevision = self::createPageRevision($page, $summary, $minor); 110 111 // save the provided data 112 $assignments = Assignments::getInstance(); 113 foreach ($valid as $v) { 114 $v->saveData($newrevision); 115 // make sure this schema is assigned 116 $assignments->assignPageSchema($page, $v->getAccessTable()->getSchema()->getTable()); 117 } 118 } 119 120 /** 121 * Save lookup data row 122 * 123 * @param AccessTable $access the table into which to save the data 124 * @param array $data data to be saved in the form of [columnName => 'data'] 125 */ 126 public function saveLookupData(AccessTable $access, $data) 127 { 128 if (!$access->getSchema()->isEditable()) { 129 throw new StructException('lookup save error: no permission for schema'); 130 } 131 $validator = $access->getValidator($data); 132 if (!$validator->validate()) { 133 throw new StructException("Validation failed:\n%s", implode("\n", $validator->getErrors())); 134 } 135 if (!$validator->saveData()) { 136 throw new StructException('No data saved'); 137 } 138 } 139 140 /** 141 * Creates a new page revision with the same page content as before 142 * 143 * @param string $page 144 * @param string $summary 145 * @param bool $minor 146 * @return int the new revision 147 */ 148 public static function createPageRevision($page, $summary = '', $minor = false) 149 { 150 $summary = trim($summary); 151 // force a new page revision @see action_plugin_struct_entry::handle_pagesave_before() 152 $GLOBALS['struct_plugin_force_page_save'] = true; 153 saveWikiText($page, rawWiki($page), $summary, $minor); 154 unset($GLOBALS['struct_plugin_force_page_save']); 155 $file = wikiFN($page); 156 clearstatcache(false, $file); 157 return filemtime($file); 158 } 159 160 /** 161 * Get info about existing schemas 162 * 163 * @param string|null $schema the schema to query, null for all 164 * @return Schema[] 165 * @throws StructException 166 */ 167 public static function getSchema($schema = null) 168 { 169 if (is_null($schema)) { 170 $schemas = Schema::getAll(); 171 } else { 172 $schemas = [$schema]; 173 } 174 175 $result = []; 176 foreach ($schemas as $table) { 177 $result[$table] = new Schema($table); 178 } 179 return $result; 180 } 181 182 /** 183 * Returns all pages known to the struct plugin 184 * 185 * That means all pages that have or had once struct data saved 186 * 187 * @param string|null $schema limit the result to a given schema 188 * @return array (page => (schema => true), ...) 189 * @throws StructException 190 */ 191 public function getPages($schema = null) 192 { 193 $assignments = Assignments::getInstance(); 194 return $assignments->getPages($schema); 195 } 196 197 /** 198 * Returns decoded JSON value or throws exception 199 * when passed parameter is not JSON 200 * 201 * @param string $value 202 * @return mixed 203 * @throws StructException 204 * @throws JsonException 205 */ 206 public static function decodeJson($value) 207 { 208 if (empty($value)) return $value; 209 if ($value[0] !== '[') throw new StructException('Lookup expects JSON'); 210 return json_decode($value, null, 512, JSON_THROW_ON_ERROR); 211 } 212} 213