Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.

Differences Between: [Versions 310 and 400] [Versions 311 and 400] [Versions 39 and 400] [Versions 400 and 401] [Versions 400 and 402] [Versions 400 and 403]

   1  <?php
   2  /**
   3   * Library for CSV serialization.
   4   *
   5   * This is used by the csv/proxy driver and is the CacheExecute()
   6   * serialization format.
   7   *
   8   * This file is part of ADOdb, a Database Abstraction Layer library for PHP.
   9   *
  10   * @package ADOdb
  11   * @link https://adodb.org Project's web site and documentation
  12   * @link https://github.com/ADOdb/ADOdb Source code and issue tracker
  13   *
  14   * The ADOdb Library is dual-licensed, released under both the BSD 3-Clause
  15   * and the GNU Lesser General Public Licence (LGPL) v2.1 or, at your option,
  16   * any later version. This means you can use it in proprietary products.
  17   * See the LICENSE.md file distributed with this source code for details.
  18   * @license BSD-3-Clause
  19   * @license LGPL-2.1-or-later
  20   *
  21   * @copyright 2000-2013 John Lim
  22   * @copyright 2014 Damien Regad, Mark Newnham and the ADOdb community
  23   */
  24  
  25  // security - hide paths
  26  if (!defined('ADODB_DIR')) die();
  27  
  28  global $ADODB_INCLUDED_CSV;
  29  $ADODB_INCLUDED_CSV = 1;
  30  
  31  	 /**
  32   	  * convert a recordset into special format
  33  	  *
  34  	  * @param rs	 the recordset
  35  	  *
  36  	  * @return	 the CSV formatted data
  37  	  */
  38  	function _rs2serialize(&$rs,$conn=false,$sql='')
  39  	 {
  40  	 	 $max = ($rs) ? $rs->FieldCount() : 0;
  41  
  42  	 	 if ($sql) $sql = urlencode($sql);
  43  	 	 // metadata setup
  44  
  45  	 	 if ($max <= 0 || $rs->dataProvider == 'empty') { // is insert/update/delete
  46  	 	 	 if (is_object($conn)) {
  47  	 	 	 	 $sql .= ','.$conn->Affected_Rows();
  48  	 	 	 	 $sql .= ','.$conn->Insert_ID();
  49  	 	 	 } else
  50  	 	 	 	 $sql .= ',,';
  51  
  52  	 	 	 $text = "====-1,0,$sql\n";
  53  	 	 	 return $text;
  54  	 	 }
  55  	 	 $tt = ($rs->timeCreated) ? $rs->timeCreated : time();
  56  
  57  	 	 ## changed format from ====0 to ====1
  58  	 	 $line = "====1,$tt,$sql\n";
  59  
  60  	 	 if ($rs->databaseType == 'array') {
  61  	 	 	 $rows = $rs->_array;
  62  	 	 } else {
  63  	 	 	 $rows = array();
  64  	 	 	 while (!$rs->EOF) {
  65  	 	 	 	 $rows[] = $rs->fields;
  66  	 	 	 	 $rs->MoveNext();
  67  	 	 	 }
  68  	 	 }
  69  
  70  	 	 for($i=0; $i < $max; $i++) {
  71  	 	 	 $o = $rs->FetchField($i);
  72  	 	 	 $flds[] = $o;
  73  	 	 }
  74  
  75  	 	 $savefetch = isset($rs->adodbFetchMode) ? $rs->adodbFetchMode : $rs->fetchMode;
  76  	 	 $class = $rs->connection->arrayClass;
  77  	 	 $rs2 = new $class();
  78  	 	 $rs2->timeCreated = $rs->timeCreated; # memcache fix
  79  	 	 $rs2->sql = $rs->sql;
  80  	 	 $rs2->oldProvider = $rs->dataProvider;
  81  	 	 $rs2->InitArrayFields($rows,$flds);
  82  	 	 $rs2->fetchMode = $savefetch;
  83  	 	 return $line.serialize($rs2);
  84  	 }
  85  
  86  
  87  /**
  88  * Open CSV file and convert it into Data.
  89  *
  90  * @param url  	 	 file/ftp/http url
  91  * @param err	 	 returns the error message
  92  * @param timeout	 dispose if recordset has been alive for $timeout secs
  93  *
  94  * @return	 	 recordset, or false if error occurred. If no
  95  *	 	 	 error occurred in sql INSERT/UPDATE/DELETE,
  96  *	 	 	 empty recordset is returned
  97  */
  98  	function csv2rs($url,&$err,$timeout=0, $rsclass='ADORecordSet_array')
  99  	 {
 100  	 	 $false = false;
 101  	 	 $err = false;
 102  	 	 $fp = @fopen($url,'rb');
 103  	 	 if (!$fp) {
 104  	 	 	 $err = $url.' file/URL not found';
 105  	 	 	 return $false;
 106  	 	 }
 107  	 	 @flock($fp, LOCK_SH);
 108  	 	 $arr = array();
 109  	 	 $ttl = 0;
 110  
 111  	 	 if ($meta = fgetcsv($fp, 32000, ",")) {
 112  	 	 	 // check if error message
 113  	 	 	 if (strncmp($meta[0],'****',4) === 0) {
 114  	 	 	 	 $err = trim(substr($meta[0],4,1024));
 115  	 	 	 	 fclose($fp);
 116  	 	 	 	 return $false;
 117  	 	 	 }
 118  	 	 	 // check for meta data
 119  	 	 	 // $meta[0] is -1 means return an empty recordset
 120  	 	 	 // $meta[1] contains a time
 121  
 122  	 	 	 if (strncmp($meta[0], '====',4) === 0) {
 123  
 124  	 	 	 	 if ($meta[0] == "====-1") {
 125  	 	 	 	 	 if (sizeof($meta) < 5) {
 126  	 	 	 	 	 	 $err = "Corrupt first line for format -1";
 127  	 	 	 	 	 	 fclose($fp);
 128  	 	 	 	 	 	 return $false;
 129  	 	 	 	 	 }
 130  	 	 	 	 	 fclose($fp);
 131  
 132  	 	 	 	 	 if ($timeout > 0) {
 133  	 	 	 	 	 	 $err = " Illegal Timeout $timeout ";
 134  	 	 	 	 	 	 return $false;
 135  	 	 	 	 	 }
 136  
 137  	 	 	 	 	 $rs = new $rsclass($val=true);
 138  	 	 	 	 	 $rs->fields = array();
 139  	 	 	 	 	 $rs->timeCreated = $meta[1];
 140  	 	 	 	 	 $rs->EOF = true;
 141  	 	 	 	 	 $rs->_numOfFields = 0;
 142  	 	 	 	 	 $rs->sql = urldecode($meta[2]);
 143  	 	 	 	 	 $rs->affectedrows = (integer)$meta[3];
 144  	 	 	 	 	 $rs->insertid = $meta[4];
 145  	 	 	 	 	 return $rs;
 146  	 	 	 	 }
 147  	 	 	 # Under high volume loads, we want only 1 thread/process to _write_file
 148  	 	 	 # so that we don't have 50 processes queueing to write the same data.
 149  	 	 	 # We use probabilistic timeout, ahead of time.
 150  	 	 	 #
 151  	 	 	 # -4 sec before timeout, give processes 1/32 chance of timing out
 152  	 	 	 # -2 sec before timeout, give processes 1/16 chance of timing out
 153  	 	 	 # -1 sec after timeout give processes 1/4 chance of timing out
 154  	 	 	 # +0 sec after timeout, give processes 100% chance of timing out
 155  	 	 	 	 if (sizeof($meta) > 1) {
 156  	 	 	 	 	 if($timeout >0){
 157  	 	 	 	 	 	 $tdiff = (integer)( $meta[1]+$timeout - time());
 158  	 	 	 	 	 	 if ($tdiff <= 2) {
 159  	 	 	 	 	 	 	 switch($tdiff) {
 160  	 	 	 	 	 	 	 case 4:
 161  	 	 	 	 	 	 	 case 3:
 162  	 	 	 	 	 	 	 	 if ((rand() & 31) == 0) {
 163  	 	 	 	 	 	 	 	 	 fclose($fp);
 164  	 	 	 	 	 	 	 	 	 $err = "Timeout 3";
 165  	 	 	 	 	 	 	 	 	 return $false;
 166  	 	 	 	 	 	 	 	 }
 167  	 	 	 	 	 	 	 	 break;
 168  	 	 	 	 	 	 	 case 2:
 169  	 	 	 	 	 	 	 	 if ((rand() & 15) == 0) {
 170  	 	 	 	 	 	 	 	 	 fclose($fp);
 171  	 	 	 	 	 	 	 	 	 $err = "Timeout 2";
 172  	 	 	 	 	 	 	 	 	 return $false;
 173  	 	 	 	 	 	 	 	 }
 174  	 	 	 	 	 	 	 	 break;
 175  	 	 	 	 	 	 	 case 1:
 176  	 	 	 	 	 	 	 	 if ((rand() & 3) == 0) {
 177  	 	 	 	 	 	 	 	 	 fclose($fp);
 178  	 	 	 	 	 	 	 	 	 $err = "Timeout 1";
 179  	 	 	 	 	 	 	 	 	 return $false;
 180  	 	 	 	 	 	 	 	 }
 181  	 	 	 	 	 	 	 	 break;
 182  	 	 	 	 	 	 	 default:
 183  	 	 	 	 	 	 	 	 fclose($fp);
 184  	 	 	 	 	 	 	 	 $err = "Timeout 0";
 185  	 	 	 	 	 	 	 	 return $false;
 186  	 	 	 	 	 	 	 } // switch
 187  
 188  	 	 	 	 	 	 } // if check flush cache
 189  	 	 	 	 	 }// (timeout>0)
 190  	 	 	 	 	 $ttl = $meta[1];
 191  	 	 	 	 }
 192  	 	 	 	 //================================================
 193  	 	 	 	 // new cache format - use serialize extensively...
 194  	 	 	 	 if ($meta[0] === '====1') {
 195  	 	 	 	 	 // slurp in the data
 196  	 	 	 	 	 $MAXSIZE = 128000;
 197  
 198  	 	 	 	 	 $text = fread($fp,$MAXSIZE);
 199  	 	 	 	 	 if (strlen($text)) {
 200  	 	 	 	 	 	 while ($txt = fread($fp,$MAXSIZE)) {
 201  	 	 	 	 	 	 	 $text .= $txt;
 202  	 	 	 	 	 	 }
 203  	 	 	 	 	 }
 204  	 	 	 	 	 fclose($fp);
 205  	 	 	 	 	 $rs = unserialize($text);
 206  	 	 	 	 	 if (is_object($rs)) $rs->timeCreated = $ttl;
 207  	 	 	 	 	 else {
 208  	 	 	 	 	 	 $err = "Unable to unserialize recordset";
 209  	 	 	 	 	 	 //echo htmlspecialchars($text),' !--END--!<p>';
 210  	 	 	 	 	 }
 211  	 	 	 	 	 return $rs;
 212  	 	 	 	 }
 213  
 214  	 	 	 	 $meta = false;
 215  	 	 	 	 $meta = fgetcsv($fp, 32000, ",");
 216  	 	 	 	 if (!$meta) {
 217  	 	 	 	 	 fclose($fp);
 218  	 	 	 	 	 $err = "Unexpected EOF 1";
 219  	 	 	 	 	 return $false;
 220  	 	 	 	 }
 221  	 	 	 }
 222  
 223  	 	 	 // Get Column definitions
 224  	 	 	 $flds = array();
 225  	 	 	 foreach($meta as $o) {
 226  	 	 	 	 $o2 = explode(':',$o);
 227  	 	 	 	 if (sizeof($o2)!=3) {
 228  	 	 	 	 	 $arr[] = $meta;
 229  	 	 	 	 	 $flds = false;
 230  	 	 	 	 	 break;
 231  	 	 	 	 }
 232  	 	 	 	 $fld = new ADOFieldObject();
 233  	 	 	 	 $fld->name = urldecode($o2[0]);
 234  	 	 	 	 $fld->type = $o2[1];
 235  	 	 	 	 $fld->max_length = $o2[2];
 236  	 	 	 	 $flds[] = $fld;
 237  	 	 	 }
 238  	 	 } else {
 239  	 	 	 fclose($fp);
 240  	 	 	 $err = "Recordset had unexpected EOF 2";
 241  	 	 	 return $false;
 242  	 	 }
 243  
 244  	 	 // slurp in the data
 245  	 	 $MAXSIZE = 128000;
 246  
 247  	 	 $text = '';
 248  	 	 while ($txt = fread($fp,$MAXSIZE)) {
 249  	 	 	 $text .= $txt;
 250  	 	 }
 251  
 252  	 	 fclose($fp);
 253  	 	 @$arr = unserialize($text);
 254  	 	 if (!is_array($arr)) {
 255  	 	 	 $err = "Recordset had unexpected EOF (in serialized recordset)";
 256  	 	 	 return $false;
 257  	 	 }
 258  	 	 $rs = new $rsclass();
 259  	 	 $rs->timeCreated = $ttl;
 260  	 	 $rs->InitArrayFields($arr,$flds);
 261  	 	 return $rs;
 262  	 }
 263  
 264  
 265  	 /**
 266  	 * Save a file $filename and its $contents (normally for caching) with file locking
 267  	 * Returns true if ok, false if fopen/fwrite error, 0 if rename error (eg. file is locked)
 268  	 */
 269  	function adodb_write_file($filename, $contents,$debug=false)
 270  	 {
 271  	 # http://www.php.net/bugs.php?id=9203 Bug that flock fails on Windows
 272  	 # So to simulate locking, we assume that rename is an atomic operation.
 273  	 # First we delete $filename, then we create a $tempfile write to it and
 274  	 # rename to the desired $filename. If the rename works, then we successfully
 275  	 # modified the file exclusively.
 276  	 # What a stupid need - having to simulate locking.
 277  	 # Risks:
 278  	 # 1. $tempfile name is not unique -- very very low
 279  	 # 2. unlink($filename) fails -- ok, rename will fail
 280  	 # 3. adodb reads stale file because unlink fails -- ok, $rs timeout occurs
 281  	 # 4. another process creates $filename between unlink() and rename() -- ok, rename() fails and  cache updated
 282  	 	 if (strncmp(PHP_OS,'WIN',3) === 0) {
 283  	 	 	 // skip the decimal place
 284  	 	 	 $mtime = substr(str_replace(' ','_',microtime()),2);
 285  	 	 	 // getmypid() actually returns 0 on Win98 - never mind!
 286  	 	 	 $tmpname = $filename.uniqid($mtime).getmypid();
 287  	 	 	 if (!($fd = @fopen($tmpname,'w'))) return false;
 288  	 	 	 if (fwrite($fd,$contents)) $ok = true;
 289  	 	 	 else $ok = false;
 290  	 	 	 fclose($fd);
 291  
 292  	 	 	 if ($ok) {
 293  	 	 	 	 @chmod($tmpname,0644);
 294  	 	 	 	 // the tricky moment
 295  	 	 	 	 @unlink($filename);
 296  	 	 	 	 if (!@rename($tmpname,$filename)) {
 297  	 	 	 	 	 @unlink($tmpname);
 298  	 	 	 	 	 $ok = 0;
 299  	 	 	 	 }
 300  	 	 	 	 if (!$ok) {
 301  	 	 	 	 	 if ($debug) ADOConnection::outp( " Rename $tmpname ".($ok? 'ok' : 'failed'));
 302  	 	 	 	 }
 303  	 	 	 }
 304  	 	 	 return $ok;
 305  	 	 }
 306  	 	 if (!($fd = @fopen($filename, 'a'))) return false;
 307  	 	 if (flock($fd, LOCK_EX) && ftruncate($fd, 0)) {
 308  	 	 	 if (fwrite( $fd, $contents )) $ok = true;
 309  	 	 	 else $ok = false;
 310  	 	 	 fclose($fd);
 311  	 	 	 @chmod($filename,0644);
 312  	 	 }else {
 313  	 	 	 fclose($fd);
 314  	 	 	 if ($debug)ADOConnection::outp( " Failed acquiring lock for $filename<br>\n");
 315  	 	 	 $ok = false;
 316  	 	 }
 317  
 318  	 	 return $ok;
 319  	 }