3 * Copyright (c) Xerox Corporation, Codendi Team, 2001-2009. All rights reserved
4 * Copyright (C) 2011 Alain Peyrat - Alcatel-Lucent
5 * Copyright 2013-2018,2021, Franck Villaume - TrivialDev
7 * This file is a part of Fusionforge.
9 * Fusionforge is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * Fusionforge is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with Fusionforge. If not, see <http://www.gnu.org/licenses/>.
23 require_once $gfcommon.'widget/WidgetLayout.class.php';
24 require_once $gfcommon.'widget/Widget.class.php';
25 require_once $gfcommon.'include/preplugins.php';
26 require_once $gfcommon.'include/utils.php';
31 * Manage layouts for users, groups and homepage
33 class WidgetLayoutManager {
36 * define constants for type of widget page
38 const OWNER_TYPE_USER = 'u';
39 const OWNER_TYPE_GROUP = 'g';
40 const OWNER_TYPE_HOME = 'h';
41 const OWNER_TYPE_TRACKER = 't';
42 const OWNER_TYPE_USERHOME = 'p';
47 * Display the default layout for the "owner". It may be the home page, the project summary page, the project tracker artifact view page, or /my/ page.
49 * @param int $owner_id
50 * @param string $owner_type
52 function displayLayout($owner_id, $owner_type) {
53 $sql = "SELECT * from owner_layouts where owner_id=$1 and owner_type=$2";
54 $res = db_query_params($sql, array($owner_id, $owner_type));
55 if($res && db_numrows($res)<1) {
56 if($owner_type == self::OWNER_TYPE_USER) {
57 $this->createDefaultLayoutForUser($owner_id);
58 $this->displayLayout($owner_id, $owner_type);
59 } elseif ($owner_type == self::OWNER_TYPE_GROUP) {
60 $this->createDefaultLayoutForProject($owner_id, 1);
61 $this->displayLayout($owner_id, $owner_type);
62 } elseif ($owner_type == self::OWNER_TYPE_HOME) {
63 $this->createDefaultLayoutForForge($owner_id);
64 $this->displayLayout($owner_id, $owner_type);
65 } elseif ($owner_type == self::OWNER_TYPE_TRACKER) {
66 $this->createDefaultLayoutForTracker($owner_id);
67 $this->displayLayout($owner_id, $owner_type);
68 } elseif ($owner_type == self::OWNER_TYPE_USERHOME) {
69 $this->createDefaultLayoutForUserHome($owner_id);
70 $this->displayLayout($owner_id, $owner_type);
74 FROM layouts AS l INNER JOIN owner_layouts AS o ON(l.id = o.layout_id)
75 WHERE o.owner_type = $1
79 $req = db_query_params($sql, array($owner_type, $owner_id));
80 if ($data = db_fetch_array($req)) {
81 $readonly = !$this->_currentUserCanUpdateLayout($owner_id, $owner_type);
82 $layout = new WidgetLayout($data['id'], $data['name'], $data['description'], $data['scope']);
83 $sql = 'SELECT * FROM layouts_rows WHERE layout_id = $1 ORDER BY rank';
84 $req_rows = db_query_params($sql, array($layout->id));
85 while ($data = db_fetch_array($req_rows)) {
86 $row = new WidgetLayout_Row($data['id'], $data['rank']);
87 $sql = 'SELECT * FROM layouts_rows_columns WHERE layout_row_id = $1';
88 $req_cols = db_query_params($sql, array($row->id));
89 while ($data = db_fetch_array($req_cols)) {
90 $col = new WidgetLayout_Row_Column($data['id'], $data['width']);
91 $sql = "SELECT * FROM layouts_contents WHERE owner_type = $1 AND owner_id = $2 AND column_id = $3 ORDER BY rank";
92 $req_content = db_query_params($sql, array($owner_type, $owner_id, $col->id));
93 while ($data = db_fetch_array($req_content)) {
94 $c = Widget::getInstance($data['name'], $owner_id);
95 if ($c && $c->isAvailable()) {
96 $c->loadContent($data['content_id']);
97 $col->add($c, $data['is_minimized'], $data['display_preferences']);
107 $layout->display($readonly, $owner_id, $owner_type);
113 * _currentUserCanUpdateLayout
115 * @param int $owner_id
116 * @param string $owner_type
117 * @return bool true if the user can update the layout (add/remove widget, collapse, set preferences, ...)
119 function _currentUserCanUpdateLayout($owner_id, $owner_type) {
121 switch ($owner_type) {
122 case self::OWNER_TYPE_USER:
123 case self::OWNER_TYPE_USERHOME:
124 if (user_getid() == $owner_id) { //Current user can only update its own /my/ page
128 case self::OWNER_TYPE_GROUP:
129 if (forge_check_perm('project_admin', $owner_id)) { //Only project admin
133 case self::OWNER_TYPE_HOME:
134 if (forge_check_global_perm('forge_admin')) { //Only site admin
138 case self::OWNER_TYPE_TRACKER:
139 if (forge_check_global_perm('tracker_admin', $owner_id)) { //Only tracker admin
149 function createDefaultLayoutForUserHome($owner_id) {
152 $sql = "INSERT INTO owner_layouts(layout_id, is_default, owner_id, owner_type) VALUES (1, 1, $1, $2)";
153 if (db_query_params($sql, array($owner_id, self::OWNER_TYPE_USERHOME))) {
155 $sql = "INSERT INTO layouts_contents(owner_id, owner_type, layout_id, column_id, name, rank) VALUES ";
157 $args[] = "($1, $2, 1, 1, 'uhpersonalinformation', 0)";
158 $args[] = "($1, $2, 1, 1, 'uhprojectinformation', 1)";
159 $args[] = "($1, $2, 1, 1, 'uhpeerratings', 2)";
160 $args[] = "($1, $2, 1, 2, 'uhactivity', 0)";
162 foreach($args as $a) {
163 if (!db_query_params($sql.$a, array($owner_id, self::OWNER_TYPE_USERHOME))) {
169 /* $em =& EventManager::instance();
171 $em->processEvent('default_widgets_for_new_owner', array('widgets' => &$widgets, 'owner_type' => self::OWNER_TYPE_USER));
172 foreach($widgets as $widget) {
173 $sql .= ",($13, $14, 1, $15, $16, $17)";
179 $success = db_error();
181 exit_error(_('DB Error')._(': ').$success, 'widgets');
186 * createDefaultLayoutForUser
188 * Create the first layout for the user and add some initial widgets:
194 * - MyMonitoredForums
195 * - and widgets of plugins if they want to listen to the event default_widgets_for_new_owner
197 * @param int $owner_id The id of the newly created user
199 function createDefaultLayoutForUser($owner_id) {
202 $sql = "INSERT INTO owner_layouts(layout_id, is_default, owner_id, owner_type) VALUES (1, 1, $1, $2)";
203 if (db_query_params($sql, array($owner_id, self::OWNER_TYPE_USER))) {
205 $sql = "INSERT INTO layouts_contents(owner_id, owner_type, layout_id, column_id, name, rank) VALUES ";
207 $args[] = "($1, $2, 1, 1, 'myprojects', 0)";
208 $args[] = "($1, $2, 1, 1, 'mybookmarks', 1)";
209 $args[] = "($1, $2, 1, 1, 'mymonitoredforums', 2)";
210 $args[] = "($1, $2, 1, 1, 'mysurveys', 4)";
211 $args[] = "($1, $2, 1, 2, 'myartifacts', 0)";
212 $args[] = "($1, $2, 1, 2, 'mymonitoredfp', 1)";
214 foreach($args as $a) {
215 if (!db_query_params($sql.$a, array($owner_id, self::OWNER_TYPE_USER))) {
221 /* $em =& EventManager::instance();
223 $em->processEvent('default_widgets_for_new_owner', array('widgets' => &$widgets, 'owner_type' => self::OWNER_TYPE_USER));
224 foreach($widgets as $widget) {
225 $sql .= ",($13, $14, 1, $15, $16, $17)";
231 $success = db_error();
233 exit_error(_('DB Error')._(': ').$success, 'widgets');
238 function createDefaultLayoutForForge($owner_id) {
241 $sql = "INSERT INTO owner_layouts (owner_id, owner_type, layout_id, is_default) values ($1, $2, $3, $4)";
242 if (db_query_params($sql, array($owner_id, self::OWNER_TYPE_HOME, 1, 1))) {
244 $sql = "INSERT INTO layouts_contents(owner_id, owner_type, layout_id, column_id, name, rank) VALUES ";
246 $args[] = "($1, $2, 1, 2, 'hometagcloud', 0)";
247 $args[] = "($1, $2, 1, 2, 'homestats', 1)";
248 $args[] = "($1, $2, 1, 2, 'homeversion', 2)";
249 $args[] = "($1, $2, 1, 1, 'homewelcome', 0)";
250 $args[] = "($1, $2, 1, 1, 'homelatestnews', 1)";
252 foreach($args as $a) {
253 if (!db_query_params($sql.$a, array(0, self::OWNER_TYPE_HOME))) {
262 $success = db_error();
264 exit_error(_('DB Error')._(': ').$success, 'widgets');
269 function createDefaultLayoutForTracker($owner_id, $template_id = 0, $newEFIds = array()) {
273 $res = db_query_params('SELECT content_id FROM layouts_contents WHERE content_id != $1 AND owner_type = $2 AND owner_id = $3', array(0, 't', $owner_id));
274 if ($res && db_numrows($res) > 0) {
275 $contentIdArr = util_result_column_to_array($res);
276 foreach ($contentIdArr as $contentId) {
277 db_query_params('DELETE FROM artifact_display_widget_field WHERE id = $1', array($contentId));
278 db_query_params('DELETE FROM artifact_display_widget WHERE id = $1 AND owner_id = $2', array($contentId, $owner_id));
281 db_query_params('DELETE FROM layouts_contents WHERE owner_id = $1 AND owner_type = $2', array($owner_id, 't'));
282 db_query_params('DELETE FROM owner_layouts WHERE owner_id = $1 AND owner_type = $2', array($owner_id, 't'));
284 $res = db_query_params('SELECT layout_id FROM owner_layouts WHERE owner_type = $1 AND owner_id = $2', array(self::OWNER_TYPE_TRACKER, $template_id));
285 if ($res && db_numrows($res) == 1) {
286 $res = db_query_params('INSERT INTO owner_layouts(layout_id, is_default, owner_id, owner_type)
287 SELECT layout_id, is_default, $1, owner_type
289 WHERE owner_type = $2
290 AND owner_id = $3', array($owner_id, self::OWNER_TYPE_TRACKER, $template_id));
295 $res = db_query_params('INSERT INTO owner_layouts (owner_id, owner_type, layout_id, is_default) values ($1, $2, $3, $4)', array($owner_id, self::OWNER_TYPE_TRACKER, 1, 1));
299 $sql = "INSERT INTO layouts_contents (owner_id, owner_type, layout_id, column_id, name, rank) VALUES ";
301 $args[] = "($1, $2, 1, 1, 'trackersummary', 1)";
302 $args[] = "($1, $2, 1, 1, 'trackermain', 2)";
303 $args[] = "($1, $2, 1, 1, 'trackerdefaultactions', 3)";
304 $args[] = "($1, $2, 1, 2, 'trackergeneral', 1)";
305 $args[] = "($1, $2, 1, 2, 'trackercomment', 2)";
307 foreach($args as $a) {
308 if (!db_query_params($sql.$a,array($owner_id, self::OWNER_TYPE_TRACKER))) {
314 // owner_id is an atid
315 $at = artifactType_get_object($owner_id);
316 $extrafields = $at->getExtraFields(array());
317 if (count($extrafields) > 0) {
318 $res = db_query_params('INSERT INTO artifact_display_widget (owner_id, title) VALUES ($1, $2)', array($owner_id, _('Default ExtraField 2-columns Widget')));
319 $content_id = db_insertid($res, 'artifact_display_widget', 'id');
322 foreach ($extrafields as $key => $extrafield) {
323 $column_id = ($key % 2) + 1; // 1 or 2
324 if ($column_id == 2) {
327 db_query_params('INSERT INTO artifact_display_widget_field (id, field_id, column_id, row_id, width, section) VALUES ($1, $2, $3, $4, $5, $6)', array($content_id, $extrafield['extra_field_id'], $column_id, $row_id, 50, ''));
329 db_query_params('INSERT INTO layouts_contents (owner_id, owner_type, layout_id, column_id, name, rank, content_id) VALUES ($1, $2, 1, 2, $3, 3, $4)',
330 array($owner_id, self::OWNER_TYPE_TRACKER, 'trackercontent', $content_id));
333 $sql = "SELECT layout_id, column_id, name, rank, is_minimized, is_removed, display_preferences, content_id
334 FROM layouts_contents
335 WHERE owner_type = $1
338 if ($req = db_query_params($sql,array(self::OWNER_TYPE_TRACKER, $template_id))) {
339 while ($data = db_fetch_array($req)) {
341 if ($data['name'] == 'trackercontent') {
342 $res = db_query_params('SELECT title FROM artifact_display_widget WHERE owner_id = $1 AND id = $2', array($template_id, $data['content_id']));
343 if ($res && db_numrows($res) > 0) {
344 $arr = db_fetch_array($res);
345 db_query_params('INSERT INTO artifact_display_widget (owner_id, title) VALUES ($1, $2)', array($owner_id, $arr['title']));
346 $content_id = db_insertid($res, 'artifact_display_widget', 'id');
347 $res2 = db_query_params('SELECT field_id, column_id, row_id, width, section FROM artifact_display_widget_field WHERE id = $1', array($data['content_id']));
348 if ($res2 && db_numrows($res2) > 0) {
349 while ($arr2 = db_fetch_array($res2)) {
350 db_query_params('INSERT INTO artifact_display_widget_field (id, field_id, column_id, row_id, width, section) VALUES ($1, $2, $3, $4, $5, $6)',
351 array($content_id, $newEFIds[$arr2['field_id']], $arr2['column_id'], $arr2['row_id'], $arr2['width'], $arr2['section']));
357 $sql = 'INSERT INTO layouts_contents (owner_id, owner_type, content_id, layout_id, column_id, name, rank, is_minimized, is_removed, display_preferences)
358 VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)';
359 db_query_params($sql, array($owner_id, self::OWNER_TYPE_TRACKER, $content_id, $data['layout_id'], $data['column_id'], $data['name'], $data['rank'], $data['is_minimized'], $data['is_removed'], $data['display_preferences']));
368 $success = db_error();
370 exit_error(_('DB Error')._(': ').$success, 'widgets');
376 * createLayoutForTrackerFromArray
378 * Create a specific layout for a new tracker, based on an descriptive array.
379 * The descriptive array is generated by getLayout function.
381 * @param int $owner_id the id of the newly created tracker
382 * @param array $layoutDescArr the descriptive array.
383 * @return bool success
385 function createLayoutForTrackerFromArray($owner_id, $layoutDescArr) {
386 if (isset($layoutDescArr['rows']) && is_array($layoutDescArr['rows'])) {
388 $res = db_query_params('SELECT content_id FROM layouts_contents WHERE content_id != $1 AND owner_type = $2 AND owner_id = $3', array(0, 't', $owner_id));
389 if ($res && db_numrows($res) > 0) {
390 $contentIdArr = util_result_column_to_array($res);
391 foreach ($contentIdArr as $contentId) {
392 db_query_params('DELETE FROM artifact_display_widget_field WHERE id = $1', array($contentId));
393 db_query_params('DELETE FROM artifact_display_widget WHERE id = $1 AND owner_id = $2', array($contentId, $owner_id));
396 db_query_params('DELETE FROM layouts_contents WHERE owner_id = $1 AND owner_type = $2', array($owner_id, 't'));
397 db_query_params('DELETE FROM owner_layouts WHERE owner_id = $1 AND owner_type = $2', array($owner_id, 't'));
398 $sql = "INSERT INTO layouts(name, description, scope) VALUES ('custom', '', 'P')";
399 if ($res = db_query_params($sql, array())) {
400 if ($new_layout_id = db_insertid($res, 'layouts', 'id')) {
401 $res = db_query_params('INSERT INTO owner_layouts (owner_id, owner_type, layout_id, is_default) values ($1, $2, $3, $4)', array($owner_id, self::OWNER_TYPE_TRACKER, $new_layout_id, 1));
403 foreach ($layoutDescArr['rows'] as $row) {
404 $sql = "INSERT INTO layouts_rows(layout_id, rank) VALUES ($1, $2)";
405 if ($res = db_query_params($sql, array($new_layout_id, $row['rank']))) {
406 if ($row_id = db_insertid($res,'layouts_rows', 'id')) {
407 if (isset($row['columns']) && is_array($row['columns'])) {
408 foreach ($row['columns'] as $column) {
409 $sql = "INSERT INTO layouts_rows_columns(layout_row_id, width) VALUES ($1, $2)";
410 db_query_params($sql, array($row_id, $column['width']));
411 $column_id = db_insertid($res,'layouts_rows_columns', 'id');
412 if (isset($column['contents']) && is_array($column['contents'])) {
413 foreach ($column['contents'] as $nkey => $nwidget) {
415 if ($nwidget['content']['id'] == 'trackercontent') {
416 $res = db_query_params('INSERT INTO artifact_display_widget (owner_id, title) VALUES ($1, $2)', array($owner_id, $nwidget['content']['trackercontent_title']));
417 $content_id = db_insertid($res, 'artifact_display_widget', 'id');
418 foreach ($nwidget['content']['layoutExtraFieldIDs'] as $efrkey => $efrow) {
419 foreach ($efrow as $efckey => $efcol) {
421 db_query_params('INSERT INTO artifact_display_widget_field (id, field_id, column_id, row_id, width, section) VALUES ($1, $2, $3, $4, $5, $6)', array($content_id, $key, $efckey, $efrkey, $efcol[$key][0], $efcol[$key][1]));
425 $sql = 'INSERT INTO layouts_contents (owner_id, owner_type, content_id, layout_id, column_id, name, rank, is_minimized, is_removed, display_preferences)
426 VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)';
427 db_query_params($sql, array($owner_id, self::OWNER_TYPE_TRACKER, $content_id, $new_layout_id, $column_id, $nwidget['content']['id'], $nkey, $nwidget['is_minimized'], 0, $nwidget['display_preferences']));
450 * createDefaultLayoutForProject
452 * Create the first layout for a new project, based on its parent template.
453 * Add some widgets based also on its parent configuration and on its service configuration.
455 * @param int $group_id the id of the newly created project
456 * @param int $template_id the id of the project template
458 function createDefaultLayoutForProject($group_id, $template_id) {
459 $project = group_get_object($group_id);
460 $sql = "INSERT INTO owner_layouts(layout_id, is_default, owner_id, owner_type)
461 SELECT layout_id, is_default, $1, owner_type
463 WHERE owner_type = $2
466 if (db_query_params($sql, array($group_id, self::OWNER_TYPE_GROUP, $template_id))) {
467 $sql = "SELECT layout_id, column_id, name, rank, is_minimized, is_removed, display_preferences, content_id
468 FROM layouts_contents
469 WHERE owner_type = $1
472 if ($req = db_query_params($sql, array(self::OWNER_TYPE_GROUP, $template_id))) {
473 while($data = db_fetch_array($req)) {
474 $w = Widget::getInstance($data['name']);
476 $w->setOwner($template_id, self::OWNER_TYPE_GROUP);
477 if ($w->canBeUsedByProject($project)) {
478 $content_id = $w->cloneContent($w->content_id, $group_id, self::OWNER_TYPE_GROUP);
479 $sql = "INSERT INTO layouts_contents(owner_id, owner_type, content_id, layout_id, column_id, name, rank, is_minimized, is_removed, display_preferences)
480 VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10);
482 db_query_params($sql, array($group_id, self::OWNER_TYPE_GROUP, $content_id, $data['layout_id'], $data['column_id'], $data['name'], $data['rank'], $data['is_minimized'], $data['is_removed'], $data['display_preferences']));
493 * createLayoutForProjectFromArray
495 * Create a specific layout for a new project, based on an descriptive array.
496 * The descriptive array is generated by getLayout function.
498 * @param int $group_id the id of the newly created project
499 * @param array $layoutDescArr the descriptive array.
500 * @return bool success
502 function createLayoutForProjectFromArray($group_id, $layoutDescArr) {
503 if (isset($layoutDescArr['rows']) && is_array($layoutDescArr['rows'])) {
504 $project = group_get_object($group_id);
505 db_query_params('DELETE FROM layouts_contents WHERE owner_id = $1 AND owner_type = $2', array($group_id, self::OWNER_TYPE_GROUP));
506 db_query_params('DELETE FROM owner_layouts WHERE owner_id = $1 AND owner_type = $2', array($group_id, self::OWNER_TYPE_GROUP));
507 $sql = "INSERT INTO layouts(name, description, scope) VALUES ('custom', '', 'P')";
508 if ($res = db_query_params($sql, array())) {
509 if ($new_layout_id = db_insertid($res, 'layouts', 'id')) {
510 $sql = 'INSERT INTO owner_layouts(layout_id, is_default, owner_id, owner_type) VALUES ($1, $2, $3, $4)';
511 if (db_query_params($sql, array($new_layout_id, 1, $group_id, self::OWNER_TYPE_GROUP))) {
512 //Create rows & columns
513 foreach($layoutDescArr['rows'] as $row) {
514 $sql = "INSERT INTO layouts_rows(layout_id, rank) VALUES ($1, $2)";
515 if ($res = db_query_params($sql, array($new_layout_id, $row['rank']))) {
516 if ($row_id = db_insertid($res,'layouts_rows', 'id')) {
517 foreach($row['columns'] as $column) {
518 $sql = "INSERT INTO layouts_rows_columns(layout_row_id, width) VALUES ($1, $2)";
519 db_query_params($sql, array($row_id, $column['width']));
520 $column_id = db_insertid($res,'layouts_rows_columns', 'id');
521 foreach ($column['contents'] as $new_widget) {
522 $sql = "INSERT INTO layouts_contents (owner_id, owner_type, content_id, layout_id, column_id, name, rank, is_minimized, is_removed, display_preferences)
523 VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10);
525 db_query_params($sql, array($group_id, self::OWNER_TYPE_GROUP, $new_widget['content']['content_id'], $new_layout_id, $column_id, $new_widget['content']['id'], 0, $new_widget['is_minimized'], 0, $new_widget['display_preferences']));
544 * displayAvailableWidgets - Display all widgets that the user can add to the layout
546 * @param int $owner_id
547 * @param string $owner_type
548 * @param int $layout_id
550 function displayAvailableWidgets($owner_id, $owner_type, $layout_id) {
552 // select already used widgets
553 $used_widgets = array();
555 FROM layouts_contents
556 WHERE owner_type = $1
559 AND content_id = 0 AND column_id <> 0";
560 $res = db_query_params($sql, array($owner_type, $owner_id, $layout_id));
561 while($data = db_fetch_array($res)) {
562 $used_widgets[] = $data['name'];
564 // build & display contextual toolbar
565 $url = '/widgets/widgets.php?owner='.getStringFromRequest('owner').
566 '&layout_id='.getIntFromRequest('layout_id');
567 $elementsLi = array();
568 $elementsLi[0]['content'] = util_make_link($url, _('Add widgets'));
569 $elementsLi[1]['content'] = util_make_link($url.'&update=layout', _('Customize Layout'));
570 $update_layout = (getStringFromRequest('update') == 'layout');
571 if ($update_layout) {
572 // customized selected
573 $elementsLi[1]['attrs'] = array('class' => 'current');
576 // add selected, or default when first displayed
577 $elementsLi[0]['attrs'] = array('class' => 'current');
580 echo $HTML->html_list($elementsLi, array('class' => 'widget_toolbar'));
581 echo $HTML->openForm(array('id' => 'builder', 'action' => '/widgets/updatelayout.php?owner='.$owner_type.$owner_id.'&action='.$action.'&layout_id='.$layout_id, 'method' => 'post'));
582 if ($update_layout) {
584 <script type='text/javascript'>//<![CDATA[
585 var controllerLayoutBuilder;
586 jQuery(document).ready(function() {
587 controllerLayoutBuilder = new LayoutBuilderController({
588 buttonAddRow: jQuery('.layout-manager-row-add'),
589 buttonAddColumn: jQuery('.layout-manager-column-add'),
590 buttonRemoveColumn: jQuery('.layout-manager-column-remove')
592 jQuery('#save').click(function(){
593 if (jQuery('#layout_custom').is(':checked')) {
594 var form = jQuery('#layout-manager').parents('form').first();
595 jQuery('#layout-manager').find('.layout-manager-row').each(function(i, e) {
598 name: 'new_layout[]',
599 value: jQuery(e).find('.layout-manager-column input[type=number]').map(function(){ return this.value;}).get().join(',')
604 jQuery('.layout-manager-chooser').each(function(i, e) {
605 jQuery(e).find('input[type=radio]').change(function() {
606 jQuery('.layout-manager-chooser').each(function(i, e) {
607 jQuery(e).removeClass('layout-manager-chooser_selected');
609 jQuery(e).addClass('layout-manager-chooser_selected');
615 $sql = "SELECT * FROM layouts WHERE scope='S' ORDER BY id ";
616 $req_layouts = db_query_params($sql, array());
617 echo $HTML->listTableTop();
619 while ($data = db_fetch_array($req_layouts)) {
620 $checked = $layout_id == $data['id'] ? 'checked="checked"' : '';
621 $is_custom = $is_custom && !$checked;
622 echo '<tr class="layout-manager-chooser '. ($checked ? 'layout-manager-chooser_selected' : '') .'" ><td>';
623 echo '<input type="radio" name="layout_id" value="'. $data['id'] .'" id="layout_'. $data['id'] .'" '. $checked .'/>';
625 echo html_e('label', array('for' => 'layout_'. $data['id']), html_image('layout/'. strtolower(preg_replace('/(\W+)/', '-', $data['name'])) .'.png'));
627 echo html_e('label', array('for' => 'layout_'. $data['id']), html_e('strong', array(), $data['name']).html_e('br').$data['description']);
630 /* Custom layout are not available yet */
631 $checked = $is_custom ? 'checked="checked"' : '';
632 echo '<tr class="layout-manager-chooser '. ($checked ? 'layout-manager-chooser_selected' : '') .'"><td>';
633 echo '<input type="radio" name="layout_id" value="-1" id="layout_custom" '. $checked .'/>';
635 echo html_e('label', array('for' => 'layout_custom'), html_image('layout/custom.png', '', '', array('style' => 'vertical-align:top;float:left;')));
637 echo html_e('label', array('for' => 'layout_custom'), html_e('strong', array(), _('Custom')).html_e('br')._('Define your own layout')._(':'));
638 echo '<table id="layout-manager">
641 <div class="layout-manager-row-add">+</div>';
642 $sql = 'SELECT * FROM layouts_rows WHERE layout_id = $1 ORDER BY rank';
643 $req_rows = db_query_params($sql,array($layout_id));
644 while ($data = db_fetch_array($req_rows)) {
645 echo '<table class="layout-manager-row">
647 <td class="layout-manager-column-add">+</td>';
648 $sql = 'SELECT * FROM layouts_rows_columns WHERE layout_row_id = $1';
649 $req_cols = db_query_params($sql,array($data['id']));
650 while ($data = db_fetch_array($req_cols)) {
651 echo '<td class="layout-manager-column">
652 <div class="layout-manager-column-remove">x</div>
653 <div class="layout-manager-column-width">
654 <input type="number" value="'. $data['width'] .'" size="1" maxlength="3" />%
657 <td class="layout-manager-column-add">+</td>';
661 echo html_e('div', array('class' => 'layout-manager-row-add'), '+');
667 echo $HTML->listTableBottom();
668 echo html_e('input', array('type' => 'submit', 'id' => 'save', 'value' => _('Submit')));
670 // display the widget selection form
676 $after .= $this->_displayWidgetsSelectionForm(Widget::getCodendiWidgets($owner_type), $used_widgets, $owner_id);
678 <td id="widget-content-categ">'. $after .'</td>
683 echo $HTML->closeForm();
686 function updateLayout($owner_id, $owner_type, $layout, $custom_layout) {
688 FROM layouts AS l INNER JOIN owner_layouts AS o ON(l.id = o.layout_id)
689 WHERE o.owner_type = $1
693 $req = db_query_params($sql, array($owner_type, $owner_id));
694 if ($data = db_fetch_array($req)) {
695 if ($this->_currentUserCanUpdateLayout($owner_id, $owner_type)) {
696 $old_scope = $data['scope'];
697 $old_layout_id = $data['id'];
698 $new_layout_id = null;
699 if ($layout == '-1' && is_array($custom_layout)) {
700 //Create a new layout based on the custom layout structure defined by the user
702 foreach($custom_layout as $widths) {
704 $cols = explode(',', $widths);
705 foreach($cols as $col) {
706 if ($width = (int)$col) {
714 //If the structure contains at least one column, create a new layout
716 $sql = "INSERT INTO layouts(name, description, scope) VALUES ('custom', '', 'P')";
717 if ($res = db_query_params($sql, array())) {
718 if ($new_layout_id = db_insertid($res, 'layouts', 'id')) {
719 //Create rows & columns
721 foreach($rows as $cols) {
722 $sql = "INSERT INTO layouts_rows(layout_id, rank) VALUES ($1, $2)";
723 if ($res = db_query_params($sql, array($new_layout_id, $rank++))) {
724 if ($row_id = db_insertid($res,'layouts_rows', 'id')) {
725 foreach($cols as $width) {
726 $sql = "INSERT INTO layouts_rows_columns(layout_row_id, width) VALUES ($1, $2)";
727 db_query_params($sql, array($row_id, $width));
736 $new_layout_id = $layout;
739 if ($new_layout_id) {
740 //Retrieve columns of old layout
741 $old = $this->_retrieveStructureOfLayout($old_layout_id);
743 //Retrieve columns of new layout
744 $new = $this->_retrieveStructureOfLayout($new_layout_id);
746 // Switch content from old columns to new columns
747 $last_new_col_id = null;
749 foreach($old['columns'] as $old_col) {
751 $new_col = current($new['columns']);
752 $last_new_col_id = $new_col['id'];
755 $sql = "UPDATE layouts_contents
756 SET layout_id = $1, column_id =$2
761 db_query_params($sql, array($new_layout_id, $last_new_col_id, $owner_type, $owner_id, $old_layout_id, $old_col['id']));
763 $sql = "UPDATE owner_layouts
765 WHERE owner_type = $2
768 db_query_params($sql, array($new_layout_id, $owner_type, $owner_id, $old_layout_id));
770 //If the old layout is custom remove it
771 if ($old_scope != 'S') {
772 $structure = $this->_retrieveStructureOfLayout($old_layout_id);
773 foreach($structure['rows'] as $row) {
774 $sql = "DELETE FROM layouts_rows WHERE id = $1";
775 db_query_params($sql, array($row['id']));
776 $sql = "DELETE FROM layouts_rows_columns WHERE layout_row_id = $1";
777 db_query_params($sql, array($row['id']));
779 $sql = "DELETE FROM layouts WHERE id = $1";
780 db_query_params($sql, array($old_layout_id));
789 function _retrieveStructureOfLayout($layout_id) {
790 $structure = array('rows' => array(), 'columns' => array());
791 $sql = 'SELECT * FROM layouts_rows WHERE layout_id = $1 ORDER BY rank';
792 $req_rows = db_query_params($sql,array($layout_id));
793 while ($row = db_fetch_array($req_rows)) {
794 $structure['rows'][] = $row;
795 $sql = 'SELECT * FROM layouts_rows_columns WHERE layout_row_id =$1 ORDER BY id';
796 $req_cols = db_query_params($sql,array($row['id']));
797 while ($col = db_fetch_array($req_cols)) {
798 $structure['columns'][] = $col;
805 * _displayWidgetsSelectionForm - displays a widget selection form
807 * @param array $widgets widgets
808 * @param array $used_widgets used widgets
809 * @param int $owner_id
812 function _displayWidgetsSelectionForm($widgets, $used_widgets, $owner_id = null) {
813 $hp = Codendi_HTMLPurifier::instance();
814 $additionnal_html = '';
815 if (count($widgets)) {
816 $categs = $this->getCategories($widgets, $owner_id);
817 $widget_rows = array();
818 if (count($categs)) {
819 // display the categories selector in left panel
820 foreach($categs as $c => $ws) {
821 $widget_rows[$c] = util_make_link('#widget-categ-'.$c, html_e('span', array(), str_replace('_',' ', $hp->purify($c, CODENDI_PURIFIER_CONVERT_HTML))), array('class' => 'widget-categ-switcher', 'id' => 'widget-categ-switcher-'.$c, 'onClick' => 'jQuery(\'.widget-categ-class-void\').hide();jQuery(\'.widget-categ-switcher\').removeClass(\'selected\');jQuery(\'#widget-categ-'. $c .'\').show();jQuery(\'#widget-categ-switcher-'. $c .'\').addClass(\'selected\')'), true);
823 uksort($widget_rows, 'strnatcasecmp');
824 echo html_ao('ul', array('id' => 'widget-categories'));
825 foreach($widget_rows as $row) {
826 echo html_e('li', array(), $row, false);
828 echo html_ac(html_ap() - 1);
829 foreach($categs as $c => $ws) {
830 $widget_rows = array();
831 // display widgets of the category
832 foreach($ws as $widget_name => $widget) {
833 $row = html_e('div', array('class' => 'widget-preview '. $widget->getPreviewCssClass()),
834 html_e('h3', array(), $widget->getTitle()).
835 html_e('p', array(), $widget->getDescription()).
836 $widget->getInstallPreferences());
837 $row .= '<div style="text-align:right; border-bottom:1px solid #ddd; padding-bottom:10px; margin-bottom:20px;">';
838 if ($widget->isUnique() && in_array($widget_name, $used_widgets)) {
839 $row .= html_e('em', array(), _('Already used'));
841 $row .= html_e('input', array('type' => 'submit', 'name' => 'name['. $widget_name .'][add]', 'value' => _('Add')));
844 $widget_rows[$widget->getTitle()] = $row;
846 uksort($widget_rows, 'strnatcasecmp');
847 $additionnal_html .= '<div id="widget-categ-'. $c .'" class="widget-categ-class-void hide" ><h2 class="boxtitle">'. str_replace('_',' ', $hp->purify($c, CODENDI_PURIFIER_CONVERT_HTML)) .'</h2>';
848 foreach($widget_rows as $row) {
849 $additionnal_html .= $row;
851 $additionnal_html .= '</div>';
855 return $additionnal_html;
859 * getCategories - sort the widgets in their different categories
861 * @param array $widgets
862 * @param int $owner_id
863 * @return array (category => widgets)
865 function getCategories($widgets, $owner_id = null) {
867 foreach($widgets as $widget_name) {
868 if ($widget = Widget::getInstance($widget_name, $owner_id)) {
869 if ($widget->isAvailable()) {
870 $category = str_replace(' ', '_', $widget->getCategory());
871 $cs = explode(',', $category);
874 if (!isset($categ[$c])) {
875 $categ[$c] = array();
877 $categ[$c][$widget_name] = $widget;
889 * @param int $owner_id
890 * @param string $owner_type
891 * @param int $layout_id
892 * @param string $name
893 * @param object $widget
895 function addWidget($owner_id, $owner_type, $layout_id, $name, &$widget) {
896 //Search for the right column. (The first used)
897 $sql = "SELECT u.column_id AS id
898 FROM layouts_contents AS u
899 LEFT JOIN (SELECT r.rank AS rank, c.id as id
900 FROM layouts_rows AS r INNER JOIN layouts_rows_columns AS c
901 ON (c.layout_row_id = r.id)
902 WHERE r.layout_id = $1) AS col
903 ON (u.column_id = col.id)
904 WHERE u.owner_type = $2
908 ORDER BY col.rank, col.id";
909 $res = db_query_params($sql, array($layout_id, $owner_type, $owner_id, $layout_id));
911 $column_id = db_result($res, 0, 'id');
913 $sql = "SELECT r.rank AS rank, c.id as id
914 FROM layouts_rows AS r
915 INNER JOIN layouts_rows_columns AS c
916 ON (c.layout_row_id = r.id)
917 WHERE r.layout_id = $1
919 $res = db_query_params($sql,array($layout_id));
920 $column_id = db_result($res, 0, 'id');
924 if ($widget->isUnique()) {
925 //unique widgets do not have content_id
928 $content_id = $widget->create();
931 //See if it already exists but not used
932 $sql = "SELECT column_id FROM layouts_contents
937 $res = db_query_params($sql, array($owner_type, $owner_id, $layout_id, $name));
939 if (db_numrows($res) && !$widget->isUnique() && db_result($res, 0, 'column_id') == 0) {
941 $sql = "SELECT min(rank) - 1 AS rank FROM layouts_contents WHERE owner_type =$1 AND owner_id = $2 AND layout_id = $3 AND column_id = $4 ";
942 $res = db_query_params($sql, array($owner_type, $owner_id, $layout_id, $column_id));
944 $rank = db_result($res, 0, 'rank');
947 $sql = "UPDATE layouts_contents
948 SET column_id = $1, rank = $2
949 WHERE owner_type = $3
953 db_query_params($sql, array($column_id, $rank, $owner_type, $owner_id, $name, $layout_id));
957 $sql = "INSERT INTO layouts_contents(owner_type, owner_id, layout_id, column_id, name, content_id, rank)
958 SELECT R1.owner_type, R1.owner_id, R1.layout_id, R1.column_id, $1, $2, coalesce(R2.rank, 1) - 1
959 FROM ( SELECT $3::character varying(1) AS owner_type, $4::integer AS owner_id, $5::integer AS layout_id, $6::integer AS column_id ) AS R1
960 LEFT JOIN layouts_contents AS R2 USING ( owner_type, owner_id, layout_id, column_id )
963 db_query_params($sql, array($name, $content_id, $owner_type, $owner_id, $layout_id, $column_id));
969 protected function feedback() {
971 $feedback .= _('Your dashboard has been updated.');
977 * @param int $owner_id
978 * @param string $owner_type
979 * @param int $layout_id
980 * @param string $name
981 * @param int $instance_id
982 * @param object $widget
984 function removeWidget($owner_id, $owner_type, $layout_id, $name, $instance_id, &$widget) {
985 $sql = "DELETE FROM layouts_contents WHERE owner_type =$1 AND owner_id = $2 AND layout_id = $3 AND name = $4 AND content_id = $5";
986 db_query_params($sql, array($owner_type, $owner_id, $layout_id, $name, $instance_id));
988 $widget->destroy($instance_id);
995 * @param int $owner_id
996 * @param string $owner_type
997 * @param int $layout_id
998 * @param string $name
999 * @param int $instance_id
1001 function mimizeWidget($owner_id, $owner_type, $layout_id, $name, $instance_id) {
1002 $sql = "UPDATE layouts_contents SET is_minimized = 1 WHERE owner_type = $1 AND owner_id = $2 AND layout_id = $3 AND name = $4 AND content_id = $5";
1003 db_query_params($sql, array($owner_type, $owner_id, $layout_id, $name, $instance_id));
1010 * @param int $owner_id
1011 * @param string $owner_type
1012 * @param int $layout_id
1013 * @param string $name
1014 * @param int $instance_id
1016 function maximizeWidget($owner_id, $owner_type, $layout_id, $name, $instance_id) {
1017 $sql = "UPDATE layouts_contents SET is_minimized = 0 WHERE owner_type =$1 AND owner_id =$2 AND layout_id = $3 AND name = $4 AND content_id = $5";
1018 db_query_params($sql, array($owner_type, $owner_id, $layout_id, $name, $instance_id));
1023 * displayWidgetPreferences
1025 * @param int $owner_id
1026 * @param string $owner_type
1027 * @param int $layout_id
1028 * @param string $name
1029 * @param int $instance_id
1031 function displayWidgetPreferences($owner_id, $owner_type, $layout_id, $name, $instance_id) {
1032 $sql = "UPDATE layouts_contents SET display_preferences = 1, is_minimized = 0 WHERE owner_type = $1 AND owner_id = $2 AND layout_id = $3 AND name = $4 AND content_id = $5";
1033 db_query_params($sql, array($owner_type, $owner_id, $layout_id, $name, $instance_id));
1038 * hideWidgetPreferences
1040 * @param int $owner_id
1041 * @param string $owner_type
1042 * @param int $layout_id
1043 * @param string $name
1044 * @param int $instance_id
1046 function hideWidgetPreferences($owner_id, $owner_type, $layout_id, $name, $instance_id) {
1047 $sql = "UPDATE layouts_contents SET display_preferences = 0 WHERE owner_type = $1 AND owner_id = $2 AND layout_id = $3 AND name = $4 AND content_id = $5";
1048 db_query_params($sql, array($owner_type, $owner_id, $layout_id, $name, $instance_id));
1055 * @param int $owner_id
1056 * @param string $owner_type
1057 * @param int $layout_id
1059 function reorderLayout($owner_id, $owner_type, $layout_id) {
1060 $keys = array_keys($_REQUEST);
1061 foreach($keys as $key) {
1062 if (preg_match('`widgetlayout_col_\d+`', $key)) {
1063 $split = explode('_', $key);
1064 $column_id = (int)$split[count($split)-1];
1067 $keyArray = getArrayFromRequest($key);
1068 foreach($keyArray as $k => $name) {
1069 list($name, $id) = explode('-', $name);
1070 $names[] = array($id, $name);
1073 //Compute differences
1074 $originals = array();
1075 $sql = "SELECT * FROM layouts_contents WHERE owner_type = $1 AND owner_id = $2 AND column_id = $3 ORDER BY rank";
1076 $res = db_query_params($sql, array($owner_type, $owner_id, $column_id));
1078 while($data = db_fetch_array($res)) {
1079 $originals[] = array($data['content_id'], $data['name']);
1082 //delete removed contents
1083 $deleted_names = utils_array_diff_names($originals, $names, );
1084 if (count($deleted_names)) {
1086 foreach($deleted_names as $id => $name) {
1092 $_and .= " (name = '".$name[1]."' AND content_id = ". $name[0] .") ";
1095 $sql = "UPDATE layouts_contents
1097 WHERE owner_type = $1
1099 AND column_id = $3". $_and;
1100 db_query_params($sql, array($owner_type, $owner_id, $column_id));
1103 //Insert new contents
1104 $added_names = utils_array_diff_names($names, $originals);
1105 if (count($added_names)) {
1107 foreach($added_names as $name) {
1113 $_and .= " (name = '".$name[1]."' AND content_id = ". $name[0] .") ";
1116 //old and new column must be part of the same layout
1117 $sql = 'UPDATE layouts_contents
1119 WHERE owner_type = $2
1120 AND owner_id = $3' . $_and ."
1121 AND layout_id = $4";
1122 db_query_params($sql, array($column_id, $owner_type, $owner_id, $layout_id));
1128 foreach($names as $name) {
1129 $sql = 'UPDATE layouts_contents SET rank = $1 WHERE owner_type =$2 AND owner_id = $3 AND column_id = $4 AND name = $5 AND content_id = $6';
1130 db_query_params($sql, array($rank++, $owner_type, $owner_id, $column_id, $name[1], $name[0]));
1136 function getLayout($owner_id, $owner_type) {
1139 FROM layouts AS l INNER JOIN owner_layouts AS o ON(l.id = o.layout_id)
1140 WHERE o.owner_type = $1
1142 AND o.is_default = 1
1144 $req = db_query_params($sql, array($owner_type, $owner_id));
1145 if ($data = db_fetch_array($req)) {
1146 $layout = new WidgetLayout($data['id'], $data['name'], $data['description'], $data['scope']);
1147 $sql = 'SELECT * FROM layouts_rows WHERE layout_id = $1 ORDER BY rank';
1148 $req_rows = db_query_params($sql,array($layout->id));
1149 while ($data = db_fetch_array($req_rows)) {
1150 $row = new WidgetLayout_Row($data['id'], $data['rank']);
1151 $sql = 'SELECT * FROM layouts_rows_columns WHERE layout_row_id = $1';
1152 $req_cols = db_query_params($sql, array($row->id));
1153 while ($data = db_fetch_array($req_cols)) {
1154 $col = new WidgetLayout_Row_Column($data['id'], $data['width']);
1155 $sql = "SELECT * FROM layouts_contents WHERE owner_type = $1 AND owner_id = $2 AND column_id = $3 ORDER BY rank";
1156 $req_content = db_query_params($sql, array($owner_type, $owner_id, $col->id));
1157 while ($data = db_fetch_array($req_content)) {
1158 $c = Widget::getInstance($data['name']);
1159 if ($c && $c->isAvailable()) {
1160 $c->loadContent($data['content_id']);
1161 $col->add($c, $data['is_minimized'], $data['display_preferences']);
1168 foreach ($row->columns as $lcol) {
1173 foreach ($layout->rows as $lrow) {
1174 unset($lrow->layout);
1178 return (array)$layout;