. */ require_once $gfcommon.'widget/WidgetLayout.class.php'; require_once $gfcommon.'widget/Widget.class.php'; require_once $gfcommon.'include/preplugins.php'; /** * WidgetLayoutManager * * Manage layouts for users, groups and homepage */ class WidgetLayoutManager { const OWNER_TYPE_USER = 'u'; /** * Layout for project home * @var string */ const OWNER_TYPE_GROUP = 'g'; const OWNER_TYPE_HOME = 'h'; /** * displayLayout * * Display the default layout for the "owner". It my be the home page, the project summary page or /my/ page. * * @param int owner_id * @param char owner_type */ function displayLayout($owner_id, $owner_type) { $sql = "SELECT * from owner_layouts where owner_id=$1 and owner_type=$2"; $res = db_query_params($sql, array($owner_id, $owner_type)); if($res && db_numrows($res)<1) { if($owner_type = self::OWNER_TYPE_USER) { $this->createDefaultLayoutForUser($owner_id); $this->displayLayout($owner_id,$owner_type); } else { $this->createDefaultLayoutForProject($owner_id,1); $this->displayLayout($owner_id,$owner_type); } } else { $sql = "SELECT l.* FROM layouts AS l INNER JOIN owner_layouts AS o ON(l.id = o.layout_id) WHERE o.owner_type = $1 AND o.owner_id = $2 AND o.is_default = 1 "; $req = db_query_params($sql, array($owner_type ,$owner_id)); if ($data = db_fetch_array($req)) { $readonly = !$this->_currentUserCanUpdateLayout($owner_id, $owner_type); // if (!$readonly) { // echo '

'. _("Customize") .'

'; // } else if ($owner_type === self::OWNER_TYPE_GROUP) { // echo '
'; // } $layout = new WidgetLayout($data['id'], $data['name'], $data['description'], $data['scope']); $sql = 'SELECT * FROM layouts_rows WHERE layout_id = $1 ORDER BY rank'; $req_rows = db_query_params($sql,array($layout->id)); while ($data = db_fetch_array($req_rows)) { $row = new WidgetLayout_Row($data['id'], $data['rank']); $sql = 'SELECT * FROM layouts_rows_columns WHERE layout_row_id = $1'; $req_cols = db_query_params($sql,array($row->id)); while ($data = db_fetch_array($req_cols)) { $col = new WidgetLayout_Row_Column($data['id'], $data['width']); $sql = "SELECT * FROM layouts_contents WHERE owner_type = $1 AND owner_id = $2 AND column_id = $3 ORDER BY rank"; $req_content = db_query_params($sql,array($owner_type, $owner_id, $col->id)); while ($data = db_fetch_array($req_content)) { $c = Widget::getInstance($data['name']); if ($c && $c->isAvailable()) { $c->loadContent($data['content_id']); $col->add($c, $data['is_minimized'], $data['display_preferences']); } unset($c); } $row->add($col); unset($col); } $layout->add($row); unset($row); } $layout->display($readonly, $owner_id, $owner_type); } } } /** * _currentUserCanUpdateLayout * * @param int owner_id * @param char owner_type * @return boolean true if the user dan uppdate the layout (add/remove widget, collapse, set preferences, ...) */ function _currentUserCanUpdateLayout($owner_id, $owner_type) { $readonly = true; $request =& HTTPRequest::instance(); switch ($owner_type) { case self::OWNER_TYPE_USER: if (user_getid() == $owner_id) { //Current user can only update its own /my/ page $readonly = false; } break; case self::OWNER_TYPE_GROUP: if (forge_check_perm('project_admin', $owner_id, NULL)) { //Only project admin $readonly = false; } break; case self::OWNER_TYPE_HOME: //Only site admin break; default: break; } return !$readonly; } /** * createDefaultLayoutForUser * * Create the first layout for the user and add some initial widgets: * - MyArtifacts * - MyProjects * - MyBookmarks * - MySurveys * - MyMonitoredFP * - MyMonitoredForums * - and widgets of plugins if they want to listen to the event default_widgets_for_new_owner * * @param int owner_id The id of the newly created user */ function createDefaultLayoutForUser($owner_id) { $owner_type = self::OWNER_TYPE_USER; $sql = "INSERT INTO owner_layouts(layout_id, is_default, owner_id, owner_type) VALUES (1, 1, $1, $2)"; if (db_query_params($sql, array($owner_id, $owner_type))) { $sql = "INSERT INTO layouts_contents(owner_id, owner_type, layout_id, column_id, name, rank) VALUES "; $args[] = "($1, $2, 1, 1, 'myprojects', 0)"; $args[] = "($1, $2, 1, 1, 'mybookmarks', 1)"; $args[] = "($1, $2, 1, 1, 'mymonitoredforums', 2)"; $args[] = "($1, $2, 1, 1, 'mysurveys', 4)"; $args[] = "($1, $2, 1, 2, 'myartifacts', 0)"; $args[] = "($1, $2, 1, 2, 'mymonitoredfp', 1)"; foreach($args as $a) { db_query_params($sql.$a,array($owner_id,$owner_type)); } /* $em =& EventManager::instance(); $widgets = array(); $em->processEvent('default_widgets_for_new_owner', array('widgets' => &$widgets, 'owner_type' => $owner_type)); foreach($widgets as $widget) { $sql .= ",($13, $14, 1, $15, $16, $17)"; }*/ } echo db_error(); } /** * createDefaultLayoutForProject * * Create the first layout for a new project, based on its parent template. * Add some widgets based also on its parent configuration and on its service configuration. * * @param int group_id the id of the newly created project * @param int template_id the id of the project template */ function createDefaultLayoutForProject($group_id, $template_id) { $pm = ProjectManager::instance(); $project = $pm->getProject($group_id); $sql = "INSERT INTO owner_layouts(layout_id, is_default, owner_id, owner_type) SELECT layout_id, is_default, $1, owner_type FROM owner_layouts WHERE owner_type = $2 AND owner_id = $3 "; if (db_query_params($sql,array($group_id, self::OWNER_TYPE_GROUP,$template_id))) { $sql = "SELECT layout_id, column_id, name, rank, is_minimized, is_removed, display_preferences, content_id FROM layouts_contents WHERE owner_type = $1 AND owner_id = $2 "; if ($req = db_query_params($sql,array( self::OWNER_TYPE_GROUP,$template_id))) { while($data = db_fetch_array($req)) { $w = Widget::getInstance($data['name']); if ($w) { $w->setOwner($template_id, self::OWNER_TYPE_GROUP); if ($w->canBeUsedByProject($project)) { $content_id = $w->cloneContent($w->content_id, $group_id, self::OWNER_TYPE_GROUP); $sql = "INSERT INTO layouts_contents(owner_id, owner_type, content_id, layout_id, column_id, name, rank, is_minimized, is_removed, display_preferences) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10); "; 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'] )); echo db_error(); } } } } } echo db_error(); } /** * displayAvailableWidgets - Display all widgets that the user can add to the layout * * @param int owner_id * @param char owner_type * @param int layout_id */ function displayAvailableWidgets($owner_id, $owner_type, $layout_id) { // select already used widgets $used_widgets = array(); $sql = "SELECT * FROM layouts_contents WHERE owner_type = $1 AND owner_id = $2 AND layout_id = $3 AND content_id = 0 AND column_id <> 0"; $res = db_query_params($sql,array($owner_type,$owner_id,$layout_id)); while($data = db_fetch_array($res)) { $used_widgets[] = $data['name']; } // display contextual toolbar echo ''; echo '
'; if ($update_layout) { $sql = "SELECT * FROM layouts WHERE scope='S' ORDER BY id "; $req_layouts = db_query_params($sql,array()); echo ''; $is_custom = true; while ($data = db_fetch_array($req_layouts)) { $checked = $layout_id == $data['id'] ? 'checked="checked"' : ''; $is_custom = $is_custom && !$checked; echo ''; } /* Custom layout are not available yet */ $checked = $is_custom ? 'checked="checked"' : ''; echo ''; echo '
'; echo ''; echo ''; echo ''; echo ''; echo ''; echo '
'; echo ''; echo ''; echo ''; echo ''; echo ''; echo '
+
'; $sql = 'SELECT * FROM layouts_rows WHERE layout_id = $1 ORDER BY rank'; $req_rows = db_query_params($sql,array($layout_id)); while ($data = db_fetch_array($req_rows)) { echo ''; $sql = 'SELECT * FROM layouts_rows_columns WHERE layout_row_id = $1'; $req_cols = db_query_params($sql,array($data['id'])); while ($data = db_fetch_array($req_cols)) { echo ''; } echo '
+
x
%
+
+
'; } echo '
'; echo '
'; echo ''; } else { // display the widget selection form $after = ''; echo '
'; echo ''; $after .= $this->_displayWidgetsSelectionForm(sprintf(_("%s Widgets"), forge_get_config('forge_name')), Widget::getCodendiWidgets($owner_type), $used_widgets); echo '
'; echo '
'. $after .'
'; } echo '
'; } function updateLayout($owner_id, $owner_type, $layout, $custom_layout) { $sql = "SELECT l.* FROM layouts AS l INNER JOIN owner_layouts AS o ON(l.id = o.layout_id) WHERE o.owner_type = $1 AND o.owner_id = $2 AND o.is_default = 1 "; $req = db_query_params($sql,array($owner_type,$owner_id)); if ($data = db_fetch_array($req)) { if ($this->_currentUserCanUpdateLayout($owner_id, $owner_type)) { $old_scope = $data['scope']; $old_layout_id = $data['id']; $new_layout_id = null; if ($layout == '-1' && is_array($custom_layout)) { //Create a new layout based on the custom layout structure defined by the user $rows = array(); foreach($custom_layout as $widths) { $row = array(); $cols = explode(',', $widths); foreach($cols as $col) { if ($width = (int)$col) { $row[] = $width; } } if (count($row)) { $rows[] = $row; } } //If the structure contains at least one column, create a new layout if (count($rows)) { $sql = "INSERT INTO layouts(name, description, scope) VALUES ('custom', '', 'P')"; if ($res = db_query_params($sql,array())) { if ($new_layout_id = db_insertid($res,'layouts', 'id')) { //Create rows & columns $rank = 0; foreach($rows as $cols) { $sql = "INSERT INTO layouts_rows(layout_id, rank) VALUES ($1,$2)"; if ($res = db_query_params($sql,array($new_layout_id,$rank++))) { if ($row_id = db_insertid($res,'layouts_rows', 'id')) { foreach($cols as $width) { $sql = "INSERT INTO layouts_rows_columns(layout_row_id, width) VALUES ($1,$2)"; db_query_params($sql,array($row_id,$width)); } } } } } } } } else { $new_layout_id = $layout; } if ($new_layout_id) { //Retrieve columns of old layout $old = $this->_retrieveStructureOfLayout($old_layout_id); //Retrieve columns of new layout $new = $this->_retrieveStructureOfLayout($new_layout_id); // Switch content from old columns to new columns $last_new_col_id = null; reset($new['columns']); foreach($old['columns'] as $old_col) { if (list(,$new_col) = each($new['columns'])) { $last_new_col_id = $new_col['id']; } $sql = "UPDATE layouts_contents SET layout_id = $1 , column_id =$2 WHERE owner_type =$3 AND owner_id =$4 AND layout_id =$5 AND column_id =$6;"; db_query_params($sql,array($new_layout_id,$last_new_col_id,$owner_type,$owner_id,$old_layout_id,$old_col['id'])); } $sql = "UPDATE owner_layouts SET layout_id = $1 WHERE owner_type = $2 AND owner_id = $3 AND layout_id = $4"; db_query_params($sql,array($new_layout_id,$owner_type,$owner_id,$old_layout_id)); //If the old layout is custom remove it if ($old_scope != 'S') { $structure = $this->_retrieveStructureOfLayout($old_layout_id); foreach($structure['rows'] as $row) { $sql = "DELETE FROM layouts_rows WHERE id = $1"; db_query_params($sql,array($row['id'])); $sql = "DELETE FROM layouts_rows_columns WHERE layout_row_id = $1"; db_query_params($sql,array($row['id'])); } $sql = "DELETE FROM layouts WHERE id = $1"; db_query_params($sql,array($old_layout_id)); } } } } $this->feedback($owner_id, $owner_type); } function _retrieveStructureOfLayout($layout_id) { $structure = array('rows' => array(), 'columns' => array()); $sql = 'SELECT * FROM layouts_rows WHERE layout_id = $1 ORDER BY rank'; $req_rows = db_query_params($sql,array($layout_id)); while ($row = db_fetch_array($req_rows)) { $structure['rows'][] = $row; $sql = 'SELECT * FROM layouts_rows_columns WHERE layout_row_id =$1 ORDER BY id'; $req_cols = db_query_params($sql,array($row['id'])); while ($col = db_fetch_array($req_cols)) { $structure['columns'][] = $col; } } return $structure; } /** * _displayWidgetsSelectionForm - displays a widget selection form * * @param title * @param widgets * @param used_widgets */ function _displayWidgetsSelectionForm($title, $widgets, $used_widgets) { $hp = Codendi_HTMLPurifier::instance(); $additionnal_html = ''; if (count($widgets)) { echo ''; $categs = $this->getCategories($widgets); $widget_rows = array(); if (count($categs)) { // display the categories selector in left panel foreach($categs as $c => $ws) { $widget_rows[$c] = ''. str_replace('-',' ', $hp->purify($c, CODENDI_PURIFIER_CONVERT_HTML)) .''; } uksort($widget_rows, 'strnatcasecmp'); echo ''; echo ''; } else { echo ''; foreach($widgets as $widget_name) { if ($widget = Widget::getInstance($widget_name)) { if ($widget->isAvailable()) { $row = ''; $row .= ''. $widget->getTitle() . $widget->getInstallPreferences() .''; $row .= ''; if ($widget->isUnique() && in_array($widget_name, $used_widgets)) { $row .= ''. _("Already used") .''; } else { $row .= ''; } $row .= ''; $widget_rows[$widget->getTitle()] = $row; } } } $i = 0; foreach($widget_rows as $row) { echo ''. $row .''; } } if (count($categs)) { foreach($categs as $c => $ws) { $i = 0; $widget_rows = array(); // display widgets of the category foreach($ws as $widget_name => $widget) { $row = ''; $row .= '
'; $row .= ''. $widget->getTitle() .''; $row .= '

'. $widget->getDescription() .'

'; $row .= $widget->getInstallPreferences(); $row .= '
'; if ($widget->isUnique() && in_array($widget_name, $used_widgets)) { $row .= ''. _("Already used") .''; } else { $row .= ''; } $row .= '
'; $widget_rows[$widget->getTitle()] = $row; } uksort($widget_rows, 'strnatcasecmp'); $additionnal_html .= '

'. $hp->purify($c, CODENDI_PURIFIER_CONVERT_HTML) .'

'; foreach($widget_rows as $row) { $additionnal_html .= $row; } $additionnal_html .= '
'; } } } return $additionnal_html; } /** * getCategories - sort the widgets in their different categories * * @param array $widgets * @return array (category => widgets) */ function getCategories($widgets) { $categ = array(); foreach($widgets as $widget_name) { if ($widget = Widget::getInstance($widget_name)) { if ($widget->isAvailable()) { $cs = explode(',', $widget->getCategory()); foreach($cs as $c) { if ($c = trim($c)) { if (!isset($categ[$c])) { $categ[$c] = array(); } $categ[$c][$widget_name] = $widget; } } } } } return $categ; } /** * addWidget * * @param int owner_id * @param char owner_type * @param int layout_id * @param string name * @param object widget * @param object request */ function addWidget($owner_id, $owner_type, $layout_id, $name, &$widget, &$request) { //Search for the right column. (The first used) $sql = "SELECT u.column_id AS id FROM layouts_contents AS u LEFT JOIN (SELECT r.rank AS rank, c.id as id FROM layouts_rows AS r INNER JOIN layouts_rows_columns AS c ON (c.layout_row_id = r.id) WHERE r.layout_id = $1) AS col ON (u.column_id = col.id) WHERE u.owner_type = $2 AND u.owner_id = $3 AND u.layout_id = $4 AND u.column_id <> 0 ORDER BY col.rank, col.id"; $res = db_query_params($sql,array($layout_id,$owner_type,$owner_id,$layout_id)); echo db_error(); $column_id = db_result($res, 0, 'id'); if (!$column_id) { $sql = "SELECT r.rank AS rank, c.id as id FROM layouts_rows AS r INNER JOIN layouts_rows_columns AS c ON (c.layout_row_id = r.id) WHERE r.layout_id = $1 ORDER BY rank, id"; $res = db_query_params($sql,array($layout_id)); $column_id = db_result($res, 0, 'id'); } //content_id if ($widget->isUnique()) { //unique widgets do not have content_id $content_id = 0; } else { $content_id = $widget->create($request); } //See if it already exists but not used $sql = "SELECT column_id FROM layouts_contents WHERE owner_type =$1 AND owner_id = $2 AND layout_id = $3 AND name = $4"; $res = db_query_params($sql,array($owner_type,$owner_id,$layout_id, $name)); echo db_error(); if (db_numrows($res) && !$widget->isUnique() && db_result($res, 0, 'column_id') == 0) { //search for rank $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 "; $res = db_query_params($sql,array($owner_type, $owner_id, $layout_id,$column_id)); echo db_error(); $rank = db_result($res, 0, 'rank'); //Update $sql = "UPDATE layouts_contents SET column_id = $1, rank = $2 WHERE owner_type = $3 AND owner_id = $4 AND name = $5 AND layout_id = $6"; $res = db_query_params($sql,array($column_id,$rank,$owner_type, $owner_id,$name, $layout_id)); echo db_error(); } else { //Insert $sql = "INSERT INTO layouts_contents(owner_type, owner_id, layout_id, column_id, name, content_id, rank) SELECT R1.owner_type, R1.owner_id, R1.layout_id, R1.column_id, $1, $2, coalesce(R2.rank, 1) - 1 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 LEFT JOIN layouts_contents AS R2 USING ( owner_type, owner_id, layout_id, column_id ) ORDER BY rank ASC LIMIT 1"; $myfile=fopen('/tmp/debug','a'); $params = array($name,$content_id,$owner_type,$owner_id,$layout_id,$column_id); fwrite($myfile, $sql." devient:\n"); fwrite($myfile, str_replace(array("$1","$2","$3","$4","$5","$6"),$params,$sql)); fwrite($myfile, "\n request content=".$request->get('content_id')); db_query_params($sql,array($name,$content_id,$owner_type,$owner_id,$layout_id,$column_id)); echo db_error(); } $this->feedback($owner_id, $owner_type); } protected function feedback($owner_id, $owner_type) { $link = '/'; if ($owner_type == self::OWNER_TYPE_GROUP) { //retrieve the short name of the project if ($project = ProjectManager::instance()->getProject($owner_id)) { $hp = Codendi_HTMLPurifier::instance(); $link = '/projects/'. $hp->purify($project->getUnixName(), CODENDI_PURIFIER_CONVERT_HTML) ; } } else if ($owner_type == self::OWNER_TYPE_USER) { $link = '/my/'; } $GLOBALS['feedback'] .= vsprintf(_('Your dashboard has been updated.'), $link); } /** * removeWidget * * @param int owner_id * @param char owner_type * @param int layout_id * @param string name * @param int instance_id */ function removeWidget($owner_id, $owner_type, $layout_id, $name, $instance_id, &$widget) { $sql = "DELETE FROM layouts_contents WHERE owner_type =$1 AND owner_id = $2 AND layout_id = $3 AND name = $4 AND content_id = $5"; db_query_params($sql,array($owner_type,$owner_id,$layout_id,$name,$instance_id)); if (!db_error()) { $widget->destroy($instance_id); } } /** * mimizeWidget * * @param int owner_id * @param char owner_type * @param int layout_id * @param string name * @param int instance_id */ function mimizeWidget($owner_id, $owner_type, $layout_id, $name, $instance_id) { $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"; db_query_params($sql,array($owner_type,$owner_id,$layout_id,$name,$instance_id)); echo db_error(); } /** * maximizeWidget * * @param int owner_id * @param char owner_type * @param int layout_id * @param string name * @param int instance_id */ function maximizeWidget($owner_id, $owner_type, $layout_id, $name, $instance_id) { $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"; db_query_params($sql,array($owner_type,$owner_id,$layout_id,$name,$instance_id)); echo db_error(); } /** * displayWidgetPreferences * * @param int owner_id * @param char owner_type * @param int layout_id * @param string name * @param int instance_id */ function displayWidgetPreferences($owner_id, $owner_type, $layout_id, $name, $instance_id) { $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"; db_query_params($sql,array($owner_type,$owner_id,$layout_id,$name,$instance_id)); echo db_error(); } /** * hideWidgetPreferences * * @param int owner_id * @param char owner_type * @param int layout_id * @param string name * @param int instance_id */ function hideWidgetPreferences($owner_id, $owner_type, $layout_id, $name, $instance_id) { $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"; db_query_params($sql,array($owner_type,$owner_id,$layout_id,$name,$instance_id)); echo db_error(); } /** * reorderLayout * * @param int owner_id * @param char owner_type * @param int layout_id * @param string name * @param int instance_id */ function reorderLayout($owner_id, $owner_type, $layout_id, &$request) { $keys = array_keys($_REQUEST); foreach($keys as $key) { if (preg_match('`widgetlayout_col_\d+`', $key)) { $split = explode('_', $key); $column_id = (int)$split[count($split)-1]; $names = array(); foreach($request->get($key) as $name) { list($name, $id) = explode('-', $name); $names[] = array($id, $name); } //Compute differences $originals = array(); $sql = "SELECT * FROM layouts_contents WHERE owner_type = $1 AND owner_id = $2 AND column_id = $3 ORDER BY rank"; $res = db_query_params($sql,array($owner_type, $owner_id, $column_id)); echo db_error(); while($data = db_fetch_array($res)) { $originals[] = array($data['content_id'], $data['name']); } //delete removed contents $deleted_names = $this->_array_diff_names($originals, $names); if (count($deleted_names)) { $_and = ''; foreach($deleted_names as $id => $name) { if ($_and) { $_and .= ' OR '; } else { $_and .= ' AND ('; } $_and .= " (name = '".$name[1]."' AND content_id = ". $name[0] .") "; } $_and .= ')'; $sql = "UPDATE layouts_contents SET column_id = 0 WHERE owner_type = $1 AND owner_id = $2 AND column_id = $3". $_and; $res = db_query_params($sql,array($owner_type, $owner_id, $column_id)); echo db_error(); } //Insert new contents $added_names = $this->_array_diff_names($names, $originals); if (count($added_names)) { $_and = ''; foreach($added_names as $name) { if ($_and) { $_and .= ' OR '; } else { $_and .= ' AND ('; } $_and .= " (name = '".$name[1]."' AND content_id = ". $name[0] .") "; } $_and .= ')'; //old and new column must be part of the same layout $sql = 'UPDATE layouts_contents SET column_id = $1 WHERE owner_type = $2 AND owner_id = $3' . $_and ." AND layout_id = $4"; $res = db_query_params($sql,array($column_id,$owner_type,$owner_id,$layout_id)); echo db_error(); } //Update ranks $rank = 0; $values = array(); foreach($names as $name) { $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'; db_query_params($sql, array($rank++,$owner_type,$owner_id,$column_id,$name[1],$name[0])); echo db_error(); } } } } /** * compute the differences between two arrays */ function _array_diff_names($tab1, $tab2) { $diff = array(); foreach($tab1 as $e1) { $found = false; reset($tab2); while(!$found && list(,$e2) = each($tab2)) { $found = !count(array_diff($e1, $e2)); } if (!$found) { $diff[] = $e1; } } return $diff; } } ?>