tagging as ATutor 1.5.4-release
[atutor.git] / tools / ims / ims_import.php
1 <?php
2 /****************************************************************/
3 /* ATutor                                                                                                               */
4 /****************************************************************/
5 /* Copyright (c) 2002-2007 by Greg Gay & Joel Kronenberg        */
6 /* Adaptive Technology Resource Centre / University of Toronto  */
7 /* http://atutor.ca                                                                                             */
8 /*                                                              */
9 /* This program is free software. You can redistribute it and/or*/
10 /* modify it under the terms of the GNU General Public License  */
11 /* as published by the Free Software Foundation.                                */
12 /****************************************************************/
13 // $Id$
14 define('AT_INCLUDE_PATH', '../../include/');
15 require(AT_INCLUDE_PATH.'vitals.inc.php');
16
17 require(AT_INCLUDE_PATH.'lib/filemanager.inc.php'); /* for clr_dir() and preImportCallBack and dirsize() */
18 require(AT_INCLUDE_PATH.'classes/pclzip.lib.php');
19
20 /* make sure we own this course that we're exporting */
21 authenticate(AT_PRIV_CONTENT);
22
23 /* to avoid timing out on large files */
24 @set_time_limit(0);
25 $_SESSION['done'] = 1;
26
27 $package_base_path = '';
28 $xml_base_path = '';
29 $element_path = array();
30 $imported_glossary = array();
31
32         /* called at the start of en element */
33         /* builds the $path array which is the path from the root to the current element */
34         function startElement($parser, $name, $attrs) {
35                 global $items, $path, $package_base_path;
36                 global $element_path;
37                 global $xml_base_path;
38                 static $current_identifier;
39
40                 if ($name == 'manifest' && isset($attrs['xml:base']) && $attrs['xml:base']) {
41                         $xml_base_path = $attrs['xml:base'];
42                 } else if ($name == 'file') {
43                         // special case for webCT content packages that don't specify the `href` attribute 
44                         // with the `<resource>` element.
45                         // we take the `href` from the first `<file>` element.
46                         if (isset($items[$current_identifier]) && ($items[$current_identifier]['href'] == '')) {
47                                 $items[$current_identifier]['href'] = $attrs['href'];
48
49                                 $items[$current_identifier]['href'] = $attrs['href'];
50
51                                 $temp_path = pathinfo($attrs['href']);
52                                 $temp_path = explode('/', $temp_path['dirname']);
53
54                                 if ($package_base_path == '') {
55                                         $package_base_path = $temp_path;
56                                 } else {
57                                         $package_base_path = array_intersect($package_base_path, $temp_path);
58                                 }
59
60                                 $items[$current_identifier]['new_path'] = implode('/', $temp_path);
61                         }
62                 } else if (($name == 'item') && ($attrs['identifierref'] != '')) {
63                         $path[] = $attrs['identifierref'];
64                 } else if (($name == 'item') && ($attrs['identifier'])) {
65                         $path[] = $attrs['identifier'];
66                 } else if (($name == 'resource') && is_array($items[$attrs['identifier']]))  {
67                         $current_identifier = $attrs['identifier'];
68
69                         if ($attrs['href']) {
70                                 $items[$attrs['identifier']]['href'] = $attrs['href'];
71
72                                 $temp_path = pathinfo($attrs['href']);
73                                 $temp_path = explode('/', $temp_path['dirname']);
74                                 if (!$package_base_path) {
75                                         $package_base_path = $temp_path;
76                                 } else {
77                                         $package_base_path = array_intersect($package_base_path, $temp_path);
78                                 }
79
80                                 $items[$attrs['identifier']]['new_path'] = implode('/', $temp_path);
81                         }
82                 }
83                 array_push($element_path, $name);
84         }
85
86         /* called when an element ends */
87         /* removed the current element from the $path */
88         function endElement($parser, $name) {
89                 global $path, $element_path, $my_data;
90
91                 if ($name == 'item') {
92                         array_pop($path);
93                 }
94
95                 if ($element_path === array('manifest', 'metadata', 'imsmd:lom', 'imsmd:general', 'imsmd:title', 'imsmd:langstring')) {
96                         global $package_base_name;
97                         $package_base_name = trim($my_data);
98                 }
99
100                 array_pop($element_path);
101                 $my_data = '';
102         }
103
104         /* called when there is character data within elements */
105         /* constructs the $items array using the last entry in $path as the parent element */
106         function characterData($parser, $data){
107                 global $path, $items, $order, $my_data, $element_path;
108
109                 $str_trimmed_data = trim($data);
110                                 
111                 if (!empty($str_trimmed_data)) {
112                         $size = count($path);
113                         if ($size > 0) {
114                                 $current_item_id = $path[$size-1];
115                                 if ($size > 1) {
116                                         $parent_item_id = $path[$size-2];
117                                 } else {
118                                         $parent_item_id = 0;
119                                 }
120                                 if (is_array($items[$current_item_id])) {
121
122                                         /* this item already exists, append the title           */
123                                         /* this fixes {\n, \t, `, &} characters in elements */
124
125                                         /* horible kludge to fix the <ns2:objectiveDesc xmlns:ns2="http://www.utoronto.ca/atrc/tile/xsd/tile_objective"> */
126                                         /* from TILE */
127                                         if ($element_path[count($element_path)-1] != 'ns1:objectiveDesc') {
128                                                 $items[$current_item_id]['title'] .= $data;
129                                         }
130         
131                                 } else {
132                                         $order[$parent_item_id] ++;
133
134                                         $items[$current_item_id] = array('title'                        => $data,
135                                                                                                         'parent_content_id' => $parent_item_id,
136                                                                                                         'ordering'                      => $order[$parent_item_id]-1);
137                                 }
138                         }
139                 }
140
141
142                 $my_data .= $data;
143         }
144
145         /* glossary parser: */
146         function glossaryStartElement($parser, $name, $attrs) {
147                 global $element_path;
148
149                 array_push($element_path, $name);
150         }
151
152         /* called when an element ends */
153         /* removed the current element from the $path */
154         function glossaryEndElement($parser, $name) {
155                 global $element_path, $my_data, $imported_glossary;
156                 static $current_term;
157
158                 if ($element_path === array('glossary', 'item', 'term')) {
159                         $current_term = $my_data;
160
161                 } else if ($element_path === array('glossary', 'item', 'definition')) {
162                         $imported_glossary[trim($current_term)] = trim($my_data);
163                 }
164
165                 array_pop($element_path);
166                 $my_data = '';
167         }
168
169         function glossaryCharacterData($parser, $data){
170                 global $my_data;
171
172                 $my_data .= $data;
173         }
174
175 if (!isset($_POST['submit']) && !isset($_POST['cancel'])) {
176         /* just a catch all */
177         
178         $errors = array('FILE_MAX_SIZE', ini_get('post_max_size'));
179         $msg->addError($errors);
180
181         header('Location: ./index.php');
182         exit;
183 } else if (isset($_POST['cancel'])) {
184         $msg->addFeedback('IMPORT_CANCELLED');
185
186         header('Location: ./index.php');
187         exit;
188 }
189
190 $cid = intval($_POST['cid']);
191
192 if (isset($_POST['url']) && ($_POST['url'] != 'http://') ) {
193         if ($content = @file_get_contents($_POST['url'])) {
194
195                 // save file to /content/
196                 $filename = substr(time(), -6). '.zip';
197                 $full_filename = AT_CONTENT_DIR . $filename;
198
199                 if (!$fp = fopen($full_filename, 'w+b')) {
200                         echo "Cannot open file ($filename)";
201                         exit;
202                 }
203
204                 if (fwrite($fp, $content, strlen($content) ) === FALSE) {
205                         echo "Cannot write to file ($filename)";
206                         exit;
207                 }
208                 fclose($fp);
209         }       
210         $_FILES['file']['name']     = $filename;
211         $_FILES['file']['tmp_name'] = $full_filename;
212         $_FILES['file']['size']     = strlen($content);
213         unset($content);
214         $url_parts = pathinfo($_POST['url']);
215         $package_base_name_url = $url_parts['basename'];
216 }
217 $ext = pathinfo($_FILES['file']['name']);
218 $ext = $ext['extension'];
219
220 if ($ext != 'zip') {
221         $msg->addError('IMPORTDIR_IMS_NOTVALID');
222 } else if ($_FILES['file']['error'] == 1) {
223         $errors = array('FILE_MAX_SIZE', ini_get('upload_max_filesize'));
224         $msg->addError($errors);
225 } else if ( !$_FILES['file']['name'] || (!is_uploaded_file($_FILES['file']['tmp_name']) && !$_POST['url'])) {
226         $msg->addError('FILE_NOT_SELECTED');
227 } else if ($_FILES['file']['size'] == 0) {
228         $msg->addError('IMPORTFILE_EMPTY');
229
230
231 if ($msg->containsErrors()) {
232         if (isset($_GET['tile'])) {
233                 header('Location: '.$_base_path.'tools/tile/index.php');
234         } else {
235                 header('Location: index.php');
236         }
237         exit;
238 }
239
240 /* check if ../content/import/ exists */
241 $import_path = AT_CONTENT_DIR . 'import/';
242 $content_path = AT_CONTENT_DIR;
243
244 if (!is_dir($import_path)) {
245         if (!@mkdir($import_path, 0700)) {
246                 $msg->addError('IMPORTDIR_FAILED');
247         }
248 }
249
250 $import_path .= $_SESSION['course_id'].'/';
251 if (is_dir($import_path)) {
252         clr_dir($import_path);
253 }
254
255 if (!@mkdir($import_path, 0700)) {
256         $msg->addError('IMPORTDIR_FAILED');
257 }
258
259 if ($msg->containsErrors()) {
260         if (isset($_GET['tile'])) {
261                 header('Location: '.$_base_path.'tools/tile/index.php');
262         } else {
263                 header('Location: index.php');
264         }
265         exit;
266 }
267
268 /* extract the entire archive into AT_COURSE_CONTENT . import/$course using the call back function to filter out php files */
269 error_reporting(0);
270 $archive = new PclZip($_FILES['file']['tmp_name']);
271 if ($archive->extract(  PCLZIP_OPT_PATH,        $import_path,
272                                                 PCLZIP_CB_PRE_EXTRACT,  'preImportCallBack') == 0) {
273         $msg->addError('IMPORT_FAILED');
274 //      echo 'Error : '.$archive->errorInfo(true);
275         clr_dir($import_path);
276         header('Location: index.php');
277         exit;
278 }
279 error_reporting(E_ALL ^ E_NOTICE);
280
281
282 /* get the course's max_quota */
283 $sql    = "SELECT max_quota FROM ".TABLE_PREFIX."courses WHERE course_id=$_SESSION[course_id]";
284 $result = mysql_query($sql, $db);
285 $q_row  = mysql_fetch_assoc($result);
286
287 if ($q_row['max_quota'] != AT_COURSESIZE_UNLIMITED) {
288
289         if ($q_row['max_quota'] == AT_COURSESIZE_DEFAULT) {
290                 $q_row['max_quota'] = $MaxCourseSize;
291         }
292         $totalBytes   = dirsize($import_path);
293         $course_total = dirsize(AT_CONTENT_DIR . $_SESSION['course_id'].'/');
294         $total_after  = $q_row['max_quota'] - $course_total - $totalBytes + $MaxCourseFloat;
295
296         if ($total_after < 0) {
297                 /* remove the content dir, since there's no space for it */
298                 $errors = array('NO_CONTENT_SPACE', number_format(-1*($total_after/AT_KBYTE_SIZE), 2 ) );
299                 $msg->addError($errors);
300                 
301                 clr_dir($import_path);
302
303                 if (isset($_GET['tile'])) {
304                         header('Location: '.$_base_path.'tools/tile/index.php');
305                 } else {
306                         header('Location: index.php');
307                 }
308                 exit;
309         }
310 }
311
312
313 $items = array(); /* all the content pages */
314 $order = array(); /* keeps track of the ordering for each content page */
315 $path  = array();  /* the hierarchy path taken in the menu to get to the current item in the manifest */
316
317 /*
318 $items[content_id/resource_id] = array(
319                                                                         'title'
320                                                                         'real_content_id' // calculated after being inserted
321                                                                         'parent_content_id'
322                                                                         'href'
323                                                                         'ordering'
324                                                                         );
325 */
326
327 $ims_manifest_xml = @file_get_contents($import_path.'imsmanifest.xml');
328
329 if ($ims_manifest_xml === false) {
330         $msg->addError('NO_IMSMANIFEST');
331
332         if (file_exists($import_path . 'atutor_backup_version')) {
333                 $msg->addError('NO_IMS_BACKUP');
334         }
335
336         clr_dir($import_path);
337
338         if (isset($_GET['tile'])) {
339                 header('Location: '.$_base_path.'tools/tile/index.php');
340         } else {
341                 header('Location: index.php');
342         }
343         exit;
344 }
345
346
347
348 // check if this is an eXe package
349 // NOTE: THIS NEEDS WORK! WHAT IS THE BEST WAY TO DETERMINE IF PACKAGE IS eXe?
350 // PERHAPS USE PARSER BELOW TO CHECK FOR ORGANIZATION?
351 $isExeContent = false;
352 $ims_organization_pos = strpos ($ims_manifest_xml, '<organization');
353 if ($ims_organization_pos !== false){
354         $ims_organization_pos = strpos ($ims_manifest_xml, 'identifier', $ims_organization_pos);
355         if ($ims_organization_pos !== false){
356                 $exe_pos = strpos ($ims_manifest_xml, 'eXenew', $ims_organization_pos);
357                 //if ($exe_pos < ($ims_organization_pos + '50')){
358                 if ($exe_pos !== false){
359                         $isExeContent = true;
360                 }
361         }
362 }
363
364
365
366
367 $xml_parser = xml_parser_create();
368
369 xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, false); /* conform to W3C specs */
370 xml_set_element_handler($xml_parser, 'startElement', 'endElement');
371 xml_set_character_data_handler($xml_parser, 'characterData');
372
373 if (!xml_parse($xml_parser, $ims_manifest_xml, true)) {
374         die(sprintf("XML error: %s at line %d",
375                                 xml_error_string(xml_get_error_code($xml_parser)),
376                                 xml_get_current_line_number($xml_parser)));
377 }
378
379
380 xml_parser_free($xml_parser);
381 //debug($items);
382
383 /* check if the glossary terms exist */
384 if (file_exists($import_path . 'glossary.xml')){
385         $glossary_xml = @file_get_contents($import_path.'glossary.xml');
386         $element_path = array();
387
388         $xml_parser = xml_parser_create();
389
390         /* insert the glossary terms into the database (if they're not in there already) */
391         /* parse the glossary.xml file and insert the terms */
392         xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, false); /* conform to W3C specs */
393         xml_set_element_handler($xml_parser, 'glossaryStartElement', 'glossaryEndElement');
394         xml_set_character_data_handler($xml_parser, 'glossaryCharacterData');
395
396         if (!xml_parse($xml_parser, $glossary_xml, true)) {
397                 debug($glossary_xml);
398                 die(sprintf("XML error: %s at line %d",
399                                         xml_error_string(xml_get_error_code($xml_parser)),
400                                         xml_get_current_line_number($xml_parser)));
401         }
402         xml_parser_free($xml_parser);
403         $contains_glossary_terms = true;
404         foreach ($imported_glossary as $term => $defn) {
405                 if (!$glossary[urlencode($term)]) {
406                         $sql = "INSERT INTO ".TABLE_PREFIX."glossary VALUES (NULL, $_SESSION[course_id], '$term', '$defn', 0)";
407                         mysql_query($sql, $db); 
408                 }
409         }
410 }
411
412
413 /* generate a unique new package base path based on the package file name and date as needed. */
414 /* the package name will be the dir where the content for this package will be put, as a result */
415 /* the 'content_path' field in the content table will be set to this path. */
416 /* $package_base_name_url comes from the URL file name (NOT the file name of the actual file we open)*/
417 if (!$package_base_name && $package_base_name_url) {
418         $package_base_name = substr($package_base_name_url, 0, -4);
419 } else if (!$package_base_name) {
420         $package_base_name = substr($_FILES['file']['name'], 0, -4);
421 }
422
423 $package_base_name = strtolower($package_base_name);
424 $package_base_name = str_replace(array('\'', '"', ' ', '|', '\\', '/', '<', '>', ':'), '_' , $package_base_name);
425 $package_base_name = preg_replace("/[^A-Za-z0-9._\-]/", '', $package_base_name);
426
427 if (is_dir(AT_CONTENT_DIR . $_SESSION['course_id'].'/'.$package_base_name)) {
428         $package_base_name .= '_'.date('ymdHis');
429 }
430
431
432 if ($package_base_path) {
433         $package_base_path = implode('/', $package_base_path);
434 }
435 if ($xml_base_path) {
436         $package_base_path = $xml_base_path . $package_base_path;
437
438         mkdir(AT_CONTENT_DIR .$_SESSION['course_id'].'/'.$xml_base_path);
439         $package_base_name = $xml_base_path . $package_base_name;
440 }
441 reset($items);
442
443 /* get the top level content ordering offset */
444 $sql    = "SELECT MAX(ordering) AS ordering FROM ".TABLE_PREFIX."content WHERE course_id=$_SESSION[course_id] AND content_parent_id=$cid";
445 $result = mysql_query($sql, $db);
446 $row    = mysql_fetch_assoc($result);
447 $order_offset = intval($row['ordering']); /* it's nice to have a real number to deal with */
448         
449         foreach ($items as $item_id => $content_info) {
450                 if (isset($content_info['href'], $xml_base_path)) {
451                         $content_info['href'] = $xml_base_path . $content_info['href'];
452                 }
453                 if (!isset($content_info['href'])) {
454                         // this item doesn't have an identifierref. so create an empty page.
455                         $content = '';
456                         $ext = '';
457                         $last_modified = date('Y-m-d H:i:s');
458                 } else {
459                         $file_info = @stat(AT_CONTENT_DIR . 'import/'.$_SESSION['course_id'].'/'.$content_info['href']);
460                         if ($file_info === false) {
461                                 continue;
462                         }
463                 
464                         $path_parts = pathinfo(AT_CONTENT_DIR . 'import/'.$_SESSION['course_id'].'/'.$content_info['href']);
465                         $ext = strtolower($path_parts['extension']);
466
467                         $last_modified = date('Y-m-d H:i:s', $file_info['mtime']);
468                 }
469                 if (in_array($ext, array('gif', 'jpg', 'bmp', 'png', 'jpeg'))) {
470                         /* this is an image */
471                         $content = '<img src="'.$content_info['href'].'" alt="'.$content_info['title'].'" />';
472                 } else if ($ext == 'swf') {
473                         /* this is flash */
474             /* Using default size of 550 x 400 */
475
476                         $content = '<object type="application/x-shockwave-flash" data="' . $content_info['href'] . '" width="550" height="400"><param name="movie" value="'. $content_info['href'] .'" /></object>';
477
478                 } else if ($ext == 'mov') {
479                         /* this is a quicktime movie  */
480             /* Using default size of 550 x 400 */
481
482                         $content = '<object classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" width="550" height="400" codebase="http://www.apple.com/qtactivex/qtplugin.cab"><param name="src" value="'. $content_info['href'] . '" /><param name="autoplay" value="true" /><param name="controller" value="true" /><embed src="' . $content_info['href'] .'" width="550" height="400" controller="true" pluginspage="http://www.apple.com/quicktime/download/"></embed></object>';
483                 } else if ($ext == 'mp3') {
484                         $content = '<object classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" width="200" height="15" codebase="http://www.apple.com/qtactivex/qtplugin.cab"><param name="src" value="'. $content_info['href'] . '" /><param name="autoplay" value="false" /><embed src="' . $content_info['href'] .'" width="200" height="15" autoplay="false" pluginspage="http://www.apple.com/quicktime/download/"></embed></object>';
485                 } else if (in_array($ext, array('wav', 'au'))) {
486                         $content = '<embed SRC="'.$content_info['href'].'" autostart="false" width="145" height="60"><noembed><bgsound src="'.$content_info['href'].'"></noembed></embed>';
487
488                 } else if (in_array($ext, array('txt', 'css', 'html', 'htm', 'csv', 'asc', 'tsv', 'xml', 'xsl'))) {
489                         /* this is a plain text file */
490                         $content = file_get_contents(AT_CONTENT_DIR . 'import/'.$_SESSION['course_id'].'/'.$content_info['href']);
491                         if ($content === false) {
492                                 /* if we can't stat() it then we're unlikely to be able to read it */
493                                 /* so we'll never get here. */
494                                 continue;
495                         }
496
497                         // get the contents of the 'head' element
498                         $head = get_html_head($content);
499                         $content = get_html_body($content);
500                         if ($contains_glossary_terms) {
501                                 // replace glossary content package links to real glossary mark-up using [?] [/?]
502                                 $content = preg_replace('/<a href="([.\w\d\s]+[^"]+)" target="body" class="at-term">([.\w\d\s&;"]+)<\/a>/i', '[?]\\2[/?]', $content);
503                         }
504
505                         /* potential security risk? */
506                         if ( strpos($content_info['href'], '..') === false) {
507                                 @unlink(AT_CONTENT_DIR . 'import/'.$_SESSION['course_id'].'/'.$content_info['href']);
508                         }
509                 } else if ($ext) {
510                         /* non text file, and can't embed (example: PDF files) */
511                         $content = '<a href="'.$content_info['href'].'">'.$content_info['title'].'</a>';
512                 }
513
514                 $content_parent_id = $cid;
515                 if ($content_info['parent_content_id'] !== 0) {
516                         $content_parent_id = $items[$content_info['parent_content_id']]['real_content_id'];
517                 }
518
519                 $my_offset = 0;
520                 if ($content_parent_id == $cid) {
521                         $my_offset = $order_offset;
522                 }
523
524                 /* replace the old path greatest common denomiator with the new package path. */
525                 /* we don't use str_replace, b/c there's no knowing what the paths may be         */
526                 /* we only want to replace the first part of the path.                                            */
527                 if ($package_base_path != '') {
528                         $content_info['new_path']       = $package_base_name . substr($content_info['new_path'], strlen($package_base_path));
529                 } else {
530                         $content_info['new_path'] = $package_base_name;
531                 }
532                 
533                 $content_info['title'] = addslashes($content_info['title']);
534                 $content = addslashes($content);
535
536                 // add a 'div' around eXe content
537                 if ($isExeContent == true){
538                         $content = '<div class=\"execontent\">\n'.$content.'\n</div>';
539                 }
540
541                 $sql= 'INSERT INTO '.TABLE_PREFIX.'content VALUES 
542                                 (NULL,  '
543                                 .$_SESSION['course_id'].','                                                                                                                     
544                                 .$content_parent_id.','         
545                                 .($content_info['ordering'] + $my_offset + 1).','
546                                 .'"'.$last_modified.'",                                                                                                 
547                                 0,1,NOW(),"","'.$content_info['new_path'].'",'
548                                 .'"'.$content_info['title'].'",'
549                                 .'"'.$content.'")';
550
551                 $result = mysql_query($sql, $db);
552
553                 // store the contents of the 'head' section
554                 /**
555                 if ($result){
556                         // get content ID
557                         $id = mysql_insert_id($db);
558                         $head = addslashes($head);
559
560                         $sql = 'INSERT INTO '.TABLE_PREFIX.'head VALUES (NULL, '.$_SESSION['course_id'].','
561                                 .$id.','
562                                 .'"'.$head.'")';
563                         $resultHead = mysql_query($sql, $db);
564                 }
565                 **/
566
567                 /* get the content id and update $items */
568                 $items[$item_id]['real_content_id'] = mysql_insert_id($db);
569         }
570
571         if ($package_base_path == '.') {
572                 $package_base_path = '';
573         }
574
575         if (@rename(AT_CONTENT_DIR . 'import/'.$_SESSION['course_id'].'/'.$package_base_path, AT_CONTENT_DIR .$_SESSION['course_id'].'/'.$package_base_name) === false) {
576                 if (!$msg->containsErrors()) {
577                         $msg->addError('IMPORT_FAILED');
578                 }
579         }
580         clr_dir(AT_CONTENT_DIR . 'import/'.$_SESSION['course_id']);
581
582         if (isset($_POST['url'])) {
583                 @unlink($full_filename);
584         }
585
586
587 if ($_POST['s_cid']){
588         if (!$msg->containsErrors()) {
589                 $msg->addFeedback('ACTION_COMPLETED_SUCCESSFULLY');
590         }
591         header('Location: ../../editor/edit_content.php?cid='.intval($_POST['cid']));
592         exit;
593 } else {
594         if (!$msg->containsErrors()) {
595                 $msg->addFeedback('ACTION_COMPLETED_SUCCESSFULLY');
596         }
597         if ($_GET['tile']) {
598                 header('Location: '.AT_BASE_HREF.'tools/tile/index.php');
599         } else {
600                 header('Location: ./index.php?cid='.intval($_POST['cid']));
601         }
602         exit;
603 }
604
605 ?>