1 | <?php |
||||
2 | /** |
||||
3 | * @package midcom.services |
||||
4 | * @author The Midgard Project, http://www.midgard-project.org |
||||
5 | * @copyright The Midgard Project, http://www.midgard-project.org |
||||
6 | * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License |
||||
7 | */ |
||||
8 | |||||
9 | /** |
||||
10 | * Metadata service. |
||||
11 | * |
||||
12 | * This service utilizes MidCOM's metadata system to provide meaningful, auto-generated |
||||
13 | * meta and link tags into documents. It is also entry point site builders can use to |
||||
14 | * retrieve metadata about current page. |
||||
15 | * |
||||
16 | * @package midcom.services |
||||
17 | */ |
||||
18 | class midcom_services_metadata |
||||
19 | { |
||||
20 | /** |
||||
21 | * The metadata currently available. This array is indexed by context id; each |
||||
22 | * value consists of a metadata object. The metadata objects are created on-demand. |
||||
23 | * |
||||
24 | * @var midcom_helper_metadata[] |
||||
25 | */ |
||||
26 | private array $_metadata = []; |
||||
27 | |||||
28 | /** |
||||
29 | * Class of the current page per each context. |
||||
30 | * Typically these are the same as the schema name of the current object's Datamanager schema. |
||||
31 | * This can be used for changing site styling based on body class="" etc. |
||||
32 | */ |
||||
33 | private array $_page_classes = []; |
||||
34 | |||||
35 | /** |
||||
36 | * Returns the view metadata of the specified context. |
||||
37 | * |
||||
38 | * @param int $context_id The context to retrieve the view metadata for, this |
||||
39 | * defaults to the current context. |
||||
40 | */ |
||||
41 | public function get_view_metadata(?int $context_id = null) : ?midcom_helper_metadata |
||||
42 | { |
||||
43 | $context_id ??= midcom_core_context::get()->id; |
||||
44 | |||||
45 | return $this->_metadata[$context_id] ?? null; |
||||
46 | } |
||||
47 | |||||
48 | /** |
||||
49 | * Sets the class of the current page for a context |
||||
50 | */ |
||||
51 | 31 | public function set_page_class(string $page_class) |
|||
52 | { |
||||
53 | 31 | $context = midcom_core_context::get(); |
|||
54 | |||||
55 | // Append current topic to page class if enabled |
||||
56 | 31 | if (midcom::get()->config->get('page_class_include_component')) { |
|||
57 | 31 | $page_class .= ' ' . str_replace('.', '_', $context->get_key(MIDCOM_CONTEXT_COMPONENT)); |
|||
58 | } |
||||
59 | |||||
60 | // Append a custom class from topic to page class |
||||
61 | 31 | $topic_class = $context->get_key(MIDCOM_CONTEXT_CONTENTTOPIC)->get_parameter('midcom.services.metadata', 'page_class'); |
|||
62 | 31 | if (!empty($topic_class)) { |
|||
63 | $page_class .= " {$topic_class}"; |
||||
64 | } |
||||
65 | |||||
66 | 31 | $this->_page_classes[$context->id] = trim($page_class); |
|||
67 | } |
||||
68 | |||||
69 | /** |
||||
70 | * Gets the CSS class of the current page of a context |
||||
71 | */ |
||||
72 | public function get_page_class() : string |
||||
73 | { |
||||
74 | $context = midcom_core_context::get(); |
||||
75 | |||||
76 | if (array_key_exists($context->id, $this->_page_classes)) { |
||||
77 | return $this->_page_classes[$context->id]; |
||||
78 | } |
||||
79 | return 'default'; |
||||
80 | } |
||||
81 | |||||
82 | /** |
||||
83 | * Get CSS classes for an object. This will include two new CSS classes for the object's class string |
||||
84 | * if appropriate: |
||||
85 | * |
||||
86 | * - unapproved: approvals are enabled for the site but the object is not translated |
||||
87 | * - hidden: object is hidden via metadata settings or scheduling |
||||
88 | */ |
||||
89 | 37 | public function get_object_classes(midcom_core_dbaobject $object, ?string $existing_classes = null) : string |
|||
90 | { |
||||
91 | 37 | $css_classes = []; |
|||
92 | 37 | if ($existing_classes !== null) { |
|||
93 | 32 | $css_classes[] = $existing_classes; |
|||
94 | } |
||||
95 | |||||
96 | // Approval attributes |
||||
97 | 37 | if ( midcom::get()->config->get('metadata_approval') |
|||
98 | 37 | && !$object->metadata->is_approved()) { |
|||
99 | $css_classes[] = 'unapproved'; |
||||
100 | } |
||||
101 | |||||
102 | // Hiding and scheduling attributes |
||||
103 | 37 | if ( ( !midcom::get()->config->get('show_hidden_objects') |
|||
104 | 37 | || midcom::get()->config->get('metadata_scheduling')) |
|||
105 | 37 | && !$object->metadata->is_visible()) { |
|||
106 | $css_classes[] = 'hidden'; |
||||
107 | } |
||||
108 | |||||
109 | // Folder's class |
||||
110 | 37 | if ( $object instanceof midcom_db_topic |
|||
111 | 37 | && $page_class = $object->get_parameter('midcom.services.metadata', 'page_class')) { |
|||
112 | $css_classes[] = $page_class; |
||||
113 | } |
||||
114 | |||||
115 | 37 | return implode(' ', $css_classes); |
|||
116 | } |
||||
117 | |||||
118 | /** |
||||
119 | * Binds view metadata to a DBA content object |
||||
120 | */ |
||||
121 | 51 | public function bind_to(midcom_core_dbaobject $object) |
|||
122 | { |
||||
123 | 51 | $context = midcom_core_context::get(); |
|||
124 | |||||
125 | 51 | $this->_metadata[$context->id] = midcom_helper_metadata::retrieve($object); |
|||
126 | 51 | if (!$this->_metadata[$context->id]) { |
|||
127 | return; |
||||
128 | } |
||||
129 | |||||
130 | // Update request metadata if appropriate |
||||
131 | 51 | $request_metadata = $this->get_request_metadata($context); |
|||
132 | 51 | $edited = $this->_metadata[$context->id]->get('revised'); |
|||
133 | 51 | if ($edited > $request_metadata['lastmodified']) { |
|||
134 | 24 | $this->set_request_metadata($edited, $request_metadata['permalinkguid']); |
|||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
135 | } |
||||
136 | } |
||||
137 | |||||
138 | /** |
||||
139 | * Populates appropriate metadata into HTML documents based on metadata information |
||||
140 | * available to MidCOM for the request. |
||||
141 | */ |
||||
142 | public function populate_meta_head() |
||||
143 | { |
||||
144 | // HTML generator information |
||||
145 | midcom::get()->head->add_meta_head(['name' => 'generator', 'content' => 'MidCOM']); |
||||
146 | |||||
147 | // If an object has been bound we have more information available |
||||
148 | if ($view_metadata = $this->get_view_metadata()) { |
||||
149 | foreach (midcom::get()->config->get_array('metadata_head_elements') as $property => $metatag) { |
||||
150 | if ($content = $view_metadata->get($property)) { |
||||
151 | // Handle date fields |
||||
152 | if (in_array($property, ['published', 'created', 'revised', 'approved', 'locked'])) { |
||||
153 | $content = gmdate('Y-m-d', (int) $content); |
||||
154 | } |
||||
155 | |||||
156 | midcom::get()->head->add_meta_head([ |
||||
157 | 'name' => $metatag, |
||||
158 | 'content' => $content, |
||||
159 | ]); |
||||
160 | } |
||||
161 | } |
||||
162 | // TODO: Add support for tags here |
||||
163 | |||||
164 | if (midcom::get()->config->get('metadata_opengraph')) { |
||||
165 | $this->_add_opengraph_metadata($view_metadata); |
||||
166 | } |
||||
167 | } |
||||
168 | } |
||||
169 | |||||
170 | private function _add_opengraph_metadata(midcom_helper_metadata $view_metadata) |
||||
171 | { |
||||
172 | $opengraph_type = $view_metadata->object->get_parameter('midcom.helper.metadata', 'opengraph_type'); |
||||
173 | if ( $opengraph_type |
||||
174 | && $opengraph_type != 'none') { |
||||
175 | $request_metadata = $this->get_request_metadata(); |
||||
176 | |||||
177 | midcom::get()->head->add_meta_head([ |
||||
178 | 'property' => 'og:type', |
||||
179 | 'content' => $opengraph_type, |
||||
180 | ]); |
||||
181 | midcom::get()->head->add_meta_head([ |
||||
182 | 'property' => 'og:title', |
||||
183 | 'content' => midcom_core_context::get()->get_key(MIDCOM_CONTEXT_PAGETITLE), |
||||
184 | ]); |
||||
185 | midcom::get()->head->add_meta_head([ |
||||
186 | 'property' => 'og:url', |
||||
187 | 'content' => $request_metadata['permalink'], |
||||
188 | ]); |
||||
189 | $opengraph_image = $view_metadata->object->get_parameter('midcom.helper.metadata', 'opengraph_image'); |
||||
190 | if (mgd_is_guid($opengraph_image)) { |
||||
191 | midcom::get()->head->add_meta_head([ |
||||
192 | 'property' => 'og:image', |
||||
193 | 'content' => midcom_db_attachment::get_url($opengraph_image), |
||||
0 ignored issues
–
show
It seems like
$opengraph_image can also be of type null ; however, parameter $attachment of midcom_db_attachment::get_url() does only seem to accept midcom_db_attachment|midgard_attachment|string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
194 | ]); |
||||
195 | } |
||||
196 | midcom::get()->head->add_meta_head([ |
||||
197 | 'property' => 'og:description', |
||||
198 | 'content' => $view_metadata->get('description'), |
||||
199 | ]); |
||||
200 | } |
||||
201 | } |
||||
202 | |||||
203 | /** |
||||
204 | * Return a list of Open Graph Protocol types |
||||
205 | * |
||||
206 | * @see http://opengraphprotocol.org/ |
||||
207 | */ |
||||
208 | 2 | public function get_opengraph_types() : array |
|||
209 | { |
||||
210 | 2 | if (!midcom::get()->config->get('metadata_opengraph')) { |
|||
211 | 2 | return []; |
|||
212 | } |
||||
213 | |||||
214 | return [ |
||||
215 | 'none' => 'opengraph type select', |
||||
216 | 'activity' => 'opengraph activity activity', |
||||
217 | 'sport' => 'opengraph activity sport', |
||||
218 | 'bar' => 'opengraph business bar', |
||||
219 | 'company' => 'opengraph business company', |
||||
220 | 'hotel' => 'opengraph business hotel', |
||||
221 | 'restaurant' => 'opengraph business restaurant', |
||||
222 | 'cause' => 'opengraph group cause', |
||||
223 | 'sports_league' => 'opengraph group sports_league', |
||||
224 | 'sports_team' => 'opengraph group sports_team', |
||||
225 | 'band' => 'opengraph organization band', |
||||
226 | 'government' => 'opengraph organization government', |
||||
227 | 'non_profit' => 'opengraph organization non_profit', |
||||
228 | 'school' => 'opengraph organization school', |
||||
229 | 'university' => 'opengraph organization university', |
||||
230 | 'actor' => 'opengraph person actor', |
||||
231 | 'athlete' => 'opengraph person athlete', |
||||
232 | 'author' => 'opengraph person author', |
||||
233 | 'director' => 'opengraph person director', |
||||
234 | 'musician' => 'opengraph person musician', |
||||
235 | 'politician' => 'opengraph person politician', |
||||
236 | 'public_figure' => 'opengraph person public_figure', |
||||
237 | 'city' => 'opengraph place city', |
||||
238 | 'country' => 'opengraph place country', |
||||
239 | 'landmark' => 'opengraph place landmark', |
||||
240 | 'state_province' => 'opengraph place state_province', |
||||
241 | 'album' => 'opengraph product album', |
||||
242 | 'book' => 'opengraph product book', |
||||
243 | 'drink' => 'opengraph product drink', |
||||
244 | 'food' => 'opengraph product food', |
||||
245 | 'game' => 'opengraph product game', |
||||
246 | 'movie' => 'opengraph product movie', |
||||
247 | 'product' => 'opengraph product product', |
||||
248 | 'song' => 'opengraph product song', |
||||
249 | 'tv_show' => 'opengraph product tv_show', |
||||
250 | 'article' => 'opengraph website article', |
||||
251 | 'blog' => 'opengraph website blog', |
||||
252 | 'website' => 'opengraph website website', |
||||
253 | ]; |
||||
254 | } |
||||
255 | |||||
256 | /** |
||||
257 | * Get the default Open Graph Protocol type for an object |
||||
258 | */ |
||||
259 | 2 | public function get_opengraph_type_default() : string |
|||
260 | { |
||||
261 | 2 | if (!midcom::get()->config->get('metadata_opengraph')) { |
|||
262 | 2 | return ''; |
|||
263 | } |
||||
264 | |||||
265 | $context = midcom_core_context::get(); |
||||
266 | if (empty($this->_metadata[$context->id])) { |
||||
267 | return ''; |
||||
268 | } |
||||
269 | $object = $this->_metadata[$context->id]->object; |
||||
270 | |||||
271 | $component = $context->get_key(MIDCOM_CONTEXT_COMPONENT); |
||||
272 | if (!$component) { |
||||
273 | return ''; |
||||
274 | } |
||||
275 | |||||
276 | $interface = midcom::get()->componentloader->get_interface_class($component); |
||||
277 | if (!method_exists($interface, 'get_opengraph_default')) { |
||||
278 | return ''; |
||||
279 | } |
||||
280 | |||||
281 | return $interface->get_opengraph_default($object); |
||||
282 | } |
||||
283 | |||||
284 | /** |
||||
285 | * Set the currently known and required Request Metadata: The last modified timestamp and the permalink GUID. |
||||
286 | * |
||||
287 | * You may set either of the arguments to null to enforce default usage (based on NAP). |
||||
288 | */ |
||||
289 | 65 | public function set_request_metadata(int $lastmodified, ?string $permalinkguid) |
|||
290 | { |
||||
291 | 65 | $context = midcom_core_context::get(); |
|||
292 | |||||
293 | 65 | $context->set_key(MIDCOM_CONTEXT_LASTMODIFIED, $lastmodified); |
|||
294 | 65 | $context->set_key(MIDCOM_CONTEXT_PERMALINKGUID, $permalinkguid); |
|||
295 | } |
||||
296 | |||||
297 | /** |
||||
298 | * Get the currently known and required Request Metadata: The last modified timestamp and the permalink GUID. |
||||
299 | * |
||||
300 | * @param midcom_core_context $context The context from which the request metadata should be retrieved. Omit |
||||
301 | * to use the current context. |
||||
302 | * @return Array An array with the two keys 'lastmodified' and 'permalinkguid' containing the |
||||
303 | * values set with the setter pendant. For ease of use, there is also a key 'permalink' |
||||
304 | * which contains a ready-made permalink. |
||||
305 | */ |
||||
306 | 51 | public function get_request_metadata(?midcom_core_context $context = null) : array |
|||
307 | { |
||||
308 | 51 | $context ??= midcom_core_context::get(); |
|||
309 | 51 | return [ |
|||
310 | 51 | 'lastmodified' => $context->get_key(MIDCOM_CONTEXT_LASTMODIFIED), |
|||
311 | 51 | 'permalinkguid' => $context->get_key(MIDCOM_CONTEXT_PERMALINKGUID), |
|||
312 | 51 | 'permalink' => midcom::get()->permalinks->create_permalink($context->get_key(MIDCOM_CONTEXT_PERMALINKGUID)), |
|||
0 ignored issues
–
show
It seems like
$context->get_key(MIDCOM_CONTEXT_PERMALINKGUID) can also be of type false ; however, parameter $guid of midcom_services_permalinks::create_permalink() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
313 | 51 | ]; |
|||
314 | } |
||||
315 | } |
||||
316 |