diff --git a/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/DateTime/Exception/InvalidTimezone.php b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/DateTime/Exception/InvalidTimezone.php
new file mode 100644
index 0000000..c2ddcac
--- /dev/null
+++ b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/DateTime/Exception/InvalidTimezone.php
@@ -0,0 +1,2 @@
+start = $start;
+ $this->end = $end;
+ if ($this->getSeconds() < 0) {
+ throw new qCal_DateTime_Exception_InvalidPeriod("The start date must come before the end date.");
+ }
+
+ }
+ /**
+ * Converts to how many seconds between the two. because this is the smallest increment
+ * used in this class, seconds are used to determine other increments
+ */
+ public function getSeconds() {
+
+ return $this->end->getUnixTimestamp() - $this->start->getUnixTimestamp();
+
+ }
+ /**
+ * Returns start date
+ */
+ public function getStart() {
+
+ return $this->start;
+
+ }
+ /**
+ * Returns end date
+ */
+ public function getEnd() {
+
+ return $this->end;
+
+ }
+
+}
\ No newline at end of file
diff --git a/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/DateTime/Recur.php b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/DateTime/Recur.php
new file mode 100644
index 0000000..c4a766e
--- /dev/null
+++ b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/DateTime/Recur.php
@@ -0,0 +1,452 @@
+ 'Monday',
+ 'TU' => 'Tuesday',
+ 'WE' => 'Wednesday',
+ 'TH' => 'Thursday',
+ 'FR' => 'Friday',
+ 'SA' => 'Saturday',
+ 'SU' => 'Sunday',
+ );
+ /**
+ * @var qCal_Date The start date/time of the recurrence
+ */
+ protected $dtstart;
+ /**
+ * @var string frequency of the recurrence
+ */
+ protected $freq;
+ /**
+ * @var qCal_Date The date/time which the recurrence ends
+ */
+ protected $until;
+ /**
+ * @var integer The amount of recurrences
+ */
+ protected $count;
+ /**
+ * @var integer Interval of recurrence (for every 3 days, "3" would be the interval)
+ */
+ protected $interval;
+ /**
+ * @var integer|array An integer between 0 and 59 (for multiple, set as an array)
+ */
+ protected $bysecond;
+ /**
+ * @var integer|array An integer between 0 and 59 (or an array of integers for multiple)
+ */
+ protected $byminute;
+ /**
+ * @var integer|array An integer or array of integers between 0 and 23
+ */
+ protected $byhour;
+ /**
+ * @var string If present, represents the nth occurrence of a specific day within monthly or yearly
+ * so it can be something like +1MO (or simply 1MO) for the first monday within the month, whereas
+ * -1MO for the last monday of the month. Or it can be simply MO to represent every monday within the month
+ */
+ protected $byday;
+ /**
+ * @var integer|array An integer or array of integers. -31 to -1 or 1 to 31. -10 would mean the tenth to last
+ * day of the month. [1,5,-5] would be the 1st, 5th, and 5th to last days of the month
+ */
+ protected $bymonthday;
+ /**
+ * @var integer|array An integer or array of integers. -366 to -1 or 1 to 366. -306 represents the 306th to last
+ * day of the year (March 1st)
+ */
+ protected $byyearday;
+ /**
+ * @var integer|array An integer or array of integers. -53 to -1 or 1 to 53. Only valid for yearly rules.
+ * 3 represents the third week of the year.
+ */
+ protected $byweekno;
+ /**
+ * @var integer|array An integer or array of integers. 1 to 12. 3 would represent March
+ */
+ protected $bymonth;
+ /**
+ * @var integer If present, it indicates the nth occurrence of the specific occurrence within the set of
+ * events specified by this recurrence rule
+ */
+ protected $bysetpos;
+ /**
+ * @var string Must be one of the weekdays specified above (2 char). Specifies the day on which the work week
+ * starts. This is significant when a weekly rule has an interval greater than 1 and a byday rule part is specified.
+ * This is also significant when in a yearly rule when a byweekno rule part is specified. Defaults to "MO"
+ */
+ protected $wkst = "MO";
+ /**
+ * Constructor
+ * @param $freq string Must be one of the freqtypes specified above.
+ * @throws qCal_Date_Exception_InvalidRecur if a frequency other than those specified above is passed in
+ */
+ public function __construct($dtstart = null) {
+
+ $this->dtstart = is_null($dtstart) ? null : qCal_DateTime::factory($dtstart);
+
+ }
+ /**
+ * Specifies the date when the recurrence stops, inclusively. If not present, and there is no count specified,
+ * then the recurrence goes on "forever".
+ * This is a getter as well as a setter (if no arg is supplied, it is a getter)
+ * @param $until string|qCal_Date|DateTime If time is specified, it must be UTC
+ * @throws qCal_Date_Exception_InvalidRecur
+ * @return self
+ */
+ public function until($until = null) {
+
+ if (is_null($until)) return $this->until;
+ if ($this->count()) throw new qCal_DateTime_Exception_InvalidRecur('A recurrence count and an until date cannot both be specified');
+ $this->until = qCal_DateTime::factory($until);
+ return $this;
+
+ }
+ /**
+ * Specifies the amount of recurrences before the recurrence ends. If neither this nor "until" is specified,
+ * the recurrence repeats "forever".
+ * This is a getter as well as a setter (if no arg is supplied, it is a getter)
+ * @param $count integer The amount of recurrences before it stops
+ * @throws qCal_Date_Exception_InvalidRecur
+ * @return self
+ */
+ public function count($count = null) {
+
+ if (is_null($count)) return $this->count;
+ if ($this->until()) throw new qCal_DateTime_Exception_InvalidRecur('A recurrence count and an until date cannot both be specified');
+ $this->count = (integer) $count;
+ return $this;
+
+ }
+ /**
+ * Specifies the start of the work-week, which is Monday by default
+ */
+ public function wkst($wkst = null) {
+
+ if (is_null($wkst)) return $this->wkst;
+ $abbrs = array_keys($this->weekdays);
+ if (!in_array($wkst, $abbrs)) throw new qCal_DateTime_Exception_InvalidRecur('"' . $wkst . '" is not a valid week day, must be one of the following: ' . implode(', ', $abbrs));
+ $this->wkst = $wkst;
+ // @todo I wonder if re-sorting the weekdays array would help me in any way...
+
+ }
+ /**
+ * Specifies the interval of recurrences
+ * This is a getter as well as a setter (if no arg is supplied, it is a getter)
+ * @param $interval integer The interval of recurrences, for instance every "3" days
+ * @throws qCal_DateTime_Exception_InvalidRecur
+ * @return self
+ */
+ public function interval($interval = null) {
+
+ if (is_null($interval)) return $this->interval;
+ $this->interval = (integer) $interval;
+ return $this;
+
+ }
+ /**
+ * Specifies a rule which will happen on every nth second.
+ * This is a getter as well as a setter (if no arg is supplied, it is a getter)
+ * @param $second integer|array Can be an integer (or array of ints) between 0 and 59
+ * @throws qCal_DateTime_Exception_InvalidRecur
+ * @return self
+ */
+ public function bySecond($second = null) {
+
+ if (is_null($second)) return $this->bysecond;
+ if (!is_array($second)) $second = array($second);
+ $this->bysecond = $second;
+ return $this;
+
+ }
+ /**
+ * Specifies a rule which will happen on every nth minute
+ * This is a getter as well as a setter (if no arg is supplied, it is a getter)
+ * @param $minute integer|array Can be an integer (or array of ints) between 0 and 59
+ * @throws qCal_DateTime_Exception_InvalidRecur
+ * @return self
+ */
+ public function byMinute($minute = null) {
+
+ if (is_null($minute)) return $this->byminute;
+ if (!is_array($minute)) $minute = array($minute);
+ $this->byminute = $minute;
+ return $this;
+
+ }
+ /**
+ * Specifies a rule which will happen on every nth hour
+ * This is a getter as well as a setter (if no arg is supplied, it is a getter)
+ * @param $hour integer|array Can be an integer (or array of ints) between 0 and 23
+ * @throws qCal_DateTime_Exception_InvalidRecur
+ * @return self
+ */
+ public function byHour($hour = null) {
+
+ if (is_null($hour)) return $this->byhour;
+ if (!is_array($hour)) $hour = array($hour);
+ $this->byhour = $hour;
+ return $this;
+
+ }
+ /**
+ * Specifies a rule which will happen on whichever day is specified. For instance, "MO" would
+ * mean every monday.
+ * This is a getter as well as a setter (if no arg is supplied, it is a getter)
+ * Sets $this->byday into an array of arrays like array('SU' => 1) for '1SU' and array('SU' => 0) for 'SU'
+ * @param $day string|array Must be one of the 2-char week days specified above. Can be preceded by
+ * a positive or negative integer to represent, for instance, the third monday of the month (3MO) or second to last
+ * Sunday of the month (-2SU)
+ * @throws qCal_DateTime_Exception_InvalidRecur
+ * @return self
+ */
+ public function byDay($day = null) {
+
+ if (is_null($day)) {
+ $ret = array();
+ foreach ($this->byday as $val) {
+ $num = (current($val) == 0) ? "" : current($val);
+ $ret[] = $num . key($val);
+ }
+ return $ret;
+ }
+ if (!is_array($day)) $day = array($day);
+ $days = array();
+ foreach ($day as $d) {
+ // optional plus or minus followed by a series of digits as group 1
+ // two-character week day as group 2
+ if (preg_match('/^([+-]?[0-9]+)?(MO|TU|WE|TH|FR|SA|SU)$/', $d, $matches)) {
+ $num = ($matches[1] == "") ? "0" : $matches[1];
+ $wday = $matches[2];
+ if (substr($num, 0, 1) == "+") {
+ $num = substr($num, 1);
+ }
+ $days[] = array($wday => $num);
+ }
+ }
+ $this->byday = $days;
+ return $this;
+
+ }
+ /**
+ * Specifies a rule which will happen on the month days specified. For instance, 23 would mean the 23rd of every month.
+ * This is a getter as well as a setter (if no arg is supplied, it is a getter)
+ * @param integer|array Must be between 1 and 31 or -31 to 1 (or an array of those values)
+ * @throws qCal_DateTime_Exception_InvalidRecur
+ * @return self
+ */
+ public function byMonthDay($monthday = null) {
+
+ if (is_null($monthday)) return $this->bymonthday;
+ if (!is_array($monthday)) $monthday = array($monthday);
+ $this->bymonthday = $monthday;
+ return $this;
+
+ }
+ /**
+ * Specifies a rule which will happen on the nth day of the year
+ * This is a getter as well as a setter (if no arg is supplied, it is a getter)
+ * @param integer|array Must be between 1 and 366 or -366 to -1.
+ * @throws qCal_DateTime_Exception_InvalidRecur
+ * @return self
+ */
+ public function byYearDay($yearday = null) {
+
+ if (is_null($yearday)) return $this->byyearday;
+ if (!is_array($yearday)) $yearday = array($yearday);
+ $this->byyearday = $yearday;
+ return $this;
+
+ }
+ /**
+ * Specifies a rule which will happen on the nth week of the year
+ * This is a getter as well as a setter (if no arg is supplied, it is a getter)
+ * @param integer|array Must be between 1 and 53 or -53 to -1.
+ * @throws qCal_DateTime_Exception_InvalidRecur
+ * @return self
+ */
+ public function byWeekNo($weekno = null) {
+
+ if (is_null($weekno)) return $this->byweekno;
+ if (!is_array($weekno)) $weekno = array($weekno);
+ $this->byweekno = $weekno;
+ return $this;
+
+ }
+ /**
+ * Specifies a rule which will happen on the nth month of the year
+ * This is a getter as well as a setter (if no arg is supplied, it is a getter)
+ * @param integer|array Must be between 1 and 12
+ * @throws qCal_DateTime_Exception_InvalidRecur
+ * @return self
+ */
+ public function byMonth($month = null) {
+
+ if (is_null($month)) return $this->bymonth;
+ if (!is_array($month)) $month = array($month);
+ $this->bymonth = $month;
+ return $this;
+
+ }
+ /**
+ * Indicates the nth occurrence of the specific occurrence within the set of
+ * events specified by the rule.
+ * This is a getter as well as a setter (if no arg is supplied, it is a getter)
+ * @todo I don't really understand how this works... :( Figure out wtf it is for.
+ * @throws qCal_DateTime_Exception_InvalidRecur
+ * @return self
+ */
+ public function bySetPos($setpos = null) {
+
+ if (is_null($setpos)) return $this->bysetpos;
+ $this->bysetpos = (integer) $setpos;
+ return $this;
+
+ }
+ /**
+ * Factory method generates the correct recur type based on the string it is passed: "yearly, weekly, etc."
+ * @param string The frequency type of recurrence rule you want to generate
+ * @param mixed The start date/time for the recurrence. Accepts anything qCal_Date accepts
+ */
+ static public function factory($freq, $start) {
+
+ $freq = ucfirst(strtolower($freq));
+ $className = "qCal_DateTime_Recur_" . $freq;
+ $fileName = str_replace("_", DIRECTORY_SEPARATOR, $className) . ".php";
+ qCal_Loader::loadFile($fileName);
+ $class = new $className($start);
+ return $class;
+
+ }
+ /**
+ * Fetches instances of the recurrence rule in the given time period. Because recurrences
+ * could potentially go on forever, there is no way to fetch ALL instances of a recurrence rule
+ * other than providing a date range that spans the entire length of the recurrence.
+ *
+ * The way this will need to work is, depending on the frequency, I will find all possible
+ * occurrence of the rule. For instance, if this is a "monthly" rule, I'll find out which month
+ * to start in, then find all occurrence possible. Then narrow down by the other rules I guess.
+ *
+ * @idea Maybe I should build classes for each of the frequency types. That way I could loop over
+ * the object and get methods like qCal_DateTime_Recur_Monthly::isNthDay('SU') to find out what sunday
+ * of the month it is... stuff like that... I dunno... ?
+ *
+ * @throws qCal_DateTime_Exception_InvalidRecur
+ * @todo The giant switch in this method is a glaring code smell, but it works for now. I will refactor
+ * after version 0.1 and remove the switch (probably will implement qCal_DateTime_Recur_Yearly, qCal_DateTime_Recur_Monthly, etc.)
+ */
+ public function getRecurrences($start, $end) {
+
+ $start = qCal_DateTime::factory($start);
+ $end = qCal_DateTime::factory($end);
+ if ($start->getUnixTimestamp() > $end->getUnixTimestamp()) throw new qCal_DateTime_Exception_InvalidRecur('Start date must come before end date');
+ if (!$this->interval) throw new qCal_DateTime_Exception_InvalidRecur('You must specify an interval');
+
+ $rules = array(
+ 'bymonth' => array(),
+ 'byweekno' => array(),
+ 'byyearday' => array(),
+ 'byday' => array(),
+ );
+
+ // byMonth rules
+ if (is_array($this->bymonth)) {
+ foreach ($this->bymonth as $bymonth) {
+ $rules['bymonth'][] = new qCal_DateTime_Recur_Rule_ByMonth($bymonth);
+ }
+ }
+
+ // byWeekNo rules
+ if (is_array($this->byweekno)) {
+ foreach ($this->byweekno as $byweekno) {
+ $rules['byweekno'][] = new qCal_DateTime_Recur_Rule_ByWeekNo($byweekno);
+ }
+ }
+
+ // byYearDay rules
+ if (is_array($this->byyearday)) {
+ foreach ($this->byyearday as $byyearday) {
+ $rules['byyearday'][] = new qCal_DateTime_Recur_Rule_ByYearDay($byyearday);
+ }
+ }
+
+ // byMonthDay rules (these get applied to bymonth rules)
+ if (is_array($this->bymonthday)) {
+ foreach ($this->bymonthday as $bymonthday) {
+ $bmdrule = new qCal_DateTime_Recur_Rule_ByMonthDay($bymonthday);
+ foreach ($rules['bymonth'] as $bymonth) {
+ $bymonth->attach($bmdrule);
+ }
+ }
+ }
+
+ // byDay rules (these get applied to bymonth rules if they exist, otherwise simply to year)
+ if (is_array($this->byday)) {
+ foreach ($this->byday as $byday) {
+ $bdrule = new qCal_DateTime_Recur_Rule_ByDay($byday);
+ if (is_array($rules['bymonth']) && !empty($rules['bymonth'])) {
+ foreach ($rules['bymonth'] as $bymonth) {
+ $bymonth->attach($bdrule);
+ }
+ } else {
+ $rules['byday'][] = $bdrule;
+ }
+ }
+ }
+
+ // byHour rules (these get applied to each rule above)
+ if (is_array($this->byhour)) {
+ foreach ($this->byhour as $byhour) {
+ $bhrule = new qCal_DateTime_Recur_Rule_ByHour($byhour);
+ foreach ($rules as $type => $ruleset) {
+ foreach ($ruleset as $rule) {
+ $rule->attach($bhrule);
+ }
+ }
+ }
+ }
+
+ // byMinute rules (these get applied to each rule above)
+ if (is_array($this->byminute)) {
+ foreach ($this->byminute as $byminute) {
+ $bmrule = new qCal_DateTime_Recur_Rule_ByMinute($byminute);
+ foreach ($rules as $type => $ruleset) {
+ foreach ($ruleset as $rule) {
+ $rule->attach($bmrule);
+ }
+ }
+ }
+ }
+
+ // bySecond rules (these get applied to each rule above)
+ if (is_array($this->bysecond)) {
+ foreach ($this->bysecond as $bysecond) {
+ $bsrule = new qCal_DateTime_Recur_Rule_BySecond($bysecond);
+ foreach ($rules as $type => $ruleset) {
+ foreach ($ruleset as $rule) {
+ $rule->attach($bsrule);
+ }
+ }
+ }
+ }
+
+ return $this->doGetRecurrences($rules, $start, $end);
+
+ }
+ /**
+ * Each type of rule needs to determine its recurrences so this is left abstract
+ * to be implemented by children.
+ */
+ abstract protected function doGetRecurrences($rules, $start, $end);
+
+}
\ No newline at end of file
diff --git a/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/DateTime/Recur/Daily.php b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/DateTime/Recur/Daily.php
new file mode 100644
index 0000000..5f6ce19
--- /dev/null
+++ b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/DateTime/Recur/Daily.php
@@ -0,0 +1,10 @@
+value = $value;
+
+ }
+ /**
+ * Attach rules to this rule. For instance, if this is a byMonth rule, then
+ * we can attach byDay rules like "-1SU" for the last Sunday of the month.
+ */
+ public function attach(qCal_DateTime_Recur_Rule $rule) {
+
+ $this->rules[] = $rule;
+
+ }
+ /**
+ * Creates the recurrences for this rule. Left to children to do this.
+ */
+ abstract public function getRecurrences();
+
+}
\ No newline at end of file
diff --git a/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/DateTime/Recur/Rule/ByDay.php b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/DateTime/Recur/Rule/ByDay.php
new file mode 100644
index 0000000..0f92132
--- /dev/null
+++ b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/DateTime/Recur/Rule/ByDay.php
@@ -0,0 +1,10 @@
+format('d');
+ $smonth = $start->format('m');
+ $syear = $start->format('Y');
+
+ // end day, year, and month
+ $eday = $end->format('d');
+ $emonth = $end->format('m');
+ $eyear = $end->format('Y');
+
+ // loop over years, by increment
+ $year = $syear;
+ while ($year <= $eyear) {
+
+ // if byMonth is specified...
+ if (count($this->byMonth())) {
+ // loop over each month
+ for ($month = 1; $month <= 12; $month++) {
+ // if this is the start year still and we haven't reached the start month, skip ahead
+ if ($year == $syear && $month < $smonth) {
+ continue;
+ }
+ // if this is the end year and we have passed the end month, break out of loop
+ if ($year == $eyear && $month > $emonth) {
+ break;
+ }
+ // if this is not one of the bymonths, continue as well
+ if (!in_array($month, $this->byMonth())) {
+ continue;
+ }
+ // now we need to loop over each day of the month to look for byday or bymonthday
+ $thismonth = new qCal_Date(); // used to determine total days in the current month
+ $thismonth->setDate($year, $month, 1);
+ $weekdays = array(
+ 'MO' => 0,
+ 'TU' => 0,
+ 'WE' => 0,
+ 'TH' => 0,
+ 'FR' => 0,
+ 'SA' => 0,
+ 'SU' => 0,
+ );
+ // @todo For now this only allows 1SU, SU, but not -1SU (no negatives for now)
+ for ($day = 1; $day <= $thismonth->format('t'); $day++) {
+ $alreadyadded = false;
+ $date = new qCal_Date;
+ $date->setDate($year, $month, $day);
+ $date->setTime(0, 0, 0);
+ $wdname = strtoupper(substr($date->format('l'), 0, 2));
+ // keep track of how many of each day of the week have gone by
+ $weekdays[$wdname]++;
+ // if byDay is specified...
+ // @todo this is inconsistent, I don't use the getter here because of its special functionality.
+ // I need to either remove the special functionality or not use getters elsewhere in this method
+ $byday = $this->byday;
+ if (count($byday)) {
+ // by day is broken into an array of arrays like array('TH' => 0), array('FR' => 1), array('MO' => -2) etc.
+ // with zero meaning every instance of that particular day should be included and number meaning the Nth of that day
+ foreach ($byday as $val) {
+ // if at least one of this wday has gone by...
+ $num = current($val);
+ if ($weekdays[$wdname] > 0) {
+ // check if it is the right week day and if a digit is specified (like 1SU) that it is checked as well
+ if ($wdname == key($val) && ($weekdays[$wdname] == $num || $num == 0)) {
+ $recurrences[] = $date;
+ $alreadyadded = true;
+ }
+ }
+ }
+ }
+
+ // if byMonthDay is specified...
+ if (count($this->byMonthDay())) {
+ foreach ($this->byMonthDay() as $mday) {
+ // only add this day if it hasn't been added already
+ if ($mday == $day && !$alreadyadded) {
+ $recurrences[] = $date;
+ }
+ }
+ }
+
+ // now loop over each hour and add hours
+ if (count($this->byHour())) {
+ $hourrecurrences = array();
+ foreach ($this->byHour() as $hour) {
+ $new = new qCal_Date();
+ $new = $new->copy($date);
+ $new->setTime($hour, 0, 0);
+ $hourrecurrences[] = $new;
+ }
+ }
+
+ // now loop over byHours and add byMinutes
+ if (count($this->byMinute())) {
+ if (!isset($minuterecurrences)) $minuterecurrences = array();
+ foreach ($this->byMinute() as $minute) {
+ $new = new qCal_Date();
+ $new = $new->copy($date);
+ $new->setTime(0, $minute, 0);
+ }
+ }
+
+ // now loop over byMinutes and add bySeconds
+
+ }
+ }
+ }
+
+ // if in the first year we don't find an instance, don't do the interval, just increment a year
+ if ($year == $syear && count($recurrences)) $year += $this->interval();
+ else ($year++);
+ }
+
+ // now loop over weeks to get byWeekNo
+
+ foreach ($recurrences as $date) {
+ // pr($date->format("r"));
+ }
+ // exit;
+
+ return $recurrences;
+ // for bymonth, it would make the most sense to loop over each month until the specified one
+ // is found. Then loop over each day to find its sub-rules.
+
+ // for byweekno, it would make the most sense to loop over each week until the specified one
+ // is found. Then apply any sub-rules (actually I'm not sure how byhour and its ilk would be applied in this situation... need to read the rfc)
+
+ }
+
+}
\ No newline at end of file
diff --git a/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/DateTime/Timezone.php b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/DateTime/Timezone.php
new file mode 100644
index 0000000..ac7cb51
--- /dev/null
+++ b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/DateTime/Timezone.php
@@ -0,0 +1,62 @@
+format($this->format);
+
+ }
+
+}
\ No newline at end of file
diff --git a/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Exception.php b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Exception.php
new file mode 100644
index 0000000..87a07a4
--- /dev/null
+++ b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Exception.php
@@ -0,0 +1,11 @@
+options = array(
+ 'searchpath' => get_include_path(),
+ );
+ $this->options = array_merge($this->options, $options);
+
+ }
+ /**
+ * @todo What should this accept? filename? actual string content? either?
+ * @todo Maybe even create a parse() for raw string and a parseFile() for a file name?
+ */
+ public function parse($content, $lexer = null) {
+
+ if (is_null($lexer)) {
+ $lexer = new qCal_Parser_Lexer_iCalendar($content);
+ }
+ $this->lexer = $lexer;
+ return $this->doParse($this->lexer->tokenize());
+
+ }
+ /**
+ * Parse a file. The searchpath defaults to the include path. Also, if the filename
+ * provided is an absolute path, the searchpath is not used. This is determined by
+ * either the file starting with a forward slash, or a drive letter (for Windows)
+ * @todo Throw an exception if file doesn't exist
+ * @todo I'm not really sure that it should default to the include path. That's not really what the include path is for, is it?
+ * @todo Test for path starting with a drive letter for windows (or find a better way to detect that)
+ */
+ public function parseFile($filename) {
+
+ // @todo This is hacky... but it works
+ if (substr($filename, 0, 1) == '/' || substr($filename, 0, 3) == 'C:\\') {
+ if (file_exists($filename)) {
+ $content = file_get_contents($filename);
+ return $this->parse($content);
+ }
+ } else {
+ $paths = explode(PATH_SEPARATOR, $this->options['searchpath']);
+ foreach ($paths as $path) {
+ $fname = $path . DIRECTORY_SEPARATOR . $filename;
+ if (file_exists($fname)) {
+ $content = file_get_contents($fname);
+ return $this->parse($content);
+ }
+ }
+ }
+ throw new qCal_Exception_FileNotFound('File cannot be found: "' . $filename . '"');
+
+ }
+ /**
+ * Override doParse in a child class if necessary
+ */
+ protected function doParse($tokens) {
+
+ $properties = array();
+ foreach ($tokens['properties'] as $propertytoken) {
+ $params = array();
+ foreach ($propertytoken['params'] as $paramtoken) {
+ $params[$paramtoken['param']] = $paramtoken['value'];
+ }
+ try {
+ $properties[] = qCal_Property::factory($propertytoken['property'], $propertytoken['value'], $params);
+ } catch (qCal_Exception $e) {
+ // @todo There should be a better way of determining what went wrong during parsing/lexing than this
+ // do nothing...
+ // pr($e);
+ }
+ }
+ $component = qCal_Component::factory($tokens['component'], $properties);
+ foreach ($tokens['children'] as $child) {
+ $childcmpnt = $this->doParse($child);
+ $component->attach($childcmpnt);
+ }
+ return $component;
+
+ }
+
+}
\ No newline at end of file
diff --git a/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Parser/Lexer.php b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Parser/Lexer.php
new file mode 100644
index 0000000..2de249a
--- /dev/null
+++ b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Parser/Lexer.php
@@ -0,0 +1,34 @@
+content = $content;
+
+ }
+ /**
+ * Tokenize content into tokens that can be used to build iCalendar objects
+ */
+ abstract public function tokenize();
+
+}
\ No newline at end of file
diff --git a/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Parser/Lexer/iCalendar.php b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Parser/Lexer/iCalendar.php
new file mode 100644
index 0000000..7e2d9cb
--- /dev/null
+++ b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Parser/Lexer/iCalendar.php
@@ -0,0 +1,120 @@
+line_terminator = chr(13) . chr(10);
+
+ }
+ /**
+ * Return a list of tokens (to be fed to the parser)
+ * @returns array tokens
+ */
+ public function tokenize() {
+
+ $lines = $this->unfold($this->content);
+ // loop through chunks of input text by separating by properties and components
+ // and create tokens for each one, creating a multi-dimensional array of tokens to return
+ $stack = array();
+ foreach ($lines as $line) {
+ // begin a component
+ if (preg_match('#^BEGIN:([a-z]+)$#i', $line, $matches)) {
+ // create new array representing the new component
+ $array = array(
+ 'component' => $matches[1],
+ 'properties' => array(),
+ 'children' => array(),
+ );
+ $stack[] = $array;
+ } elseif (strpos($line, "END:") === 0) {
+ // end component, pop the stack
+ $child = array_pop($stack);
+ if (empty($stack)) {
+ $tokens = $child;
+ } else {
+ $parent =& $stack[count($stack)-1];
+ array_push($parent['children'], $child);
+ }
+ } else {
+ // continue component
+ if (preg_match('#^([^:]+):"?([^\n]+)?"?$#i', $line, $matches)) {
+ // @todo What do I do with empty values?
+ $value = isset($matches[2]) ? $matches[2] : "";
+ $component =& $stack[count($stack)-1];
+ // if line is a property line, start a new property, but first determine if there are any params
+ $property = $matches[1];
+ $params = array();
+ $propparts = explode(";", $matches[1]);
+ if (count($propparts) > 1) {
+ foreach ($propparts as $key => $part) {
+ // the first one is the property name
+ if ($key == 0) {
+ $property = $part;
+ } else {
+ // the rest are params
+ // @todo Quoted param values need to be taken care of...
+ list($paramname, $paramvalue) = explode("=", $part, 2);
+ $params[] = array(
+ 'param' => $paramname,
+ 'value' => $paramvalue,
+ );
+ }
+ }
+ }
+ $proparray = array(
+ 'property' => $property,
+ 'value' => $value,
+ 'params' => $params,
+ );
+ $component['properties'][] = $proparray;
+ }
+ }
+ }
+ return $tokens;
+
+ }
+ /**
+ * Unfold the file before trying to parse it
+ */
+ protected function unfold($content) {
+
+ $return = array();
+ $lines = explode($this->line_terminator, $content);
+ foreach ($lines as $line) {
+ $checkempty = trim($line);
+ if (empty($checkempty)) continue;
+ $chr1 = substr($line, 0, 1);
+ $therest = substr($line, 1);
+ // if character 1 is a whitespace character... (tab or space)
+ if ($chr1 == chr(9) || $chr1 == chr(32)) {
+ $return[count($return)-1] .= $therest;
+ } else {
+ $return[] = $line;
+ }
+ }
+ return $return;
+
+ }
+
+}
\ No newline at end of file
diff --git a/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Property.php b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Property.php
new file mode 100644
index 0000000..a3b50fe
--- /dev/null
+++ b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Property.php
@@ -0,0 +1,289 @@
+type)
+ * @todo Determine if there can be multiple params of the same name
+ */
+ public function __construct($value = null, $params = array()) {
+
+ if (is_null($this->name)) $this->name = $this->getPropertyNameFromClassName(get_class($this));
+ foreach ($params as $pname => $pval) {
+ $this->setParam($pname, $pval);
+ }
+ // this must be set after parameters because the VALUE parameter can affect it
+ $this->setValue($value);
+
+ }
+ /**
+ * Generates a qCal_Property class based on property name, params, and value
+ * which can come directly from an icalendar file
+ * @todo I need a way to detect INVALID properties as they are being parsed. This
+ * way there can be an option to NOT stop on errors. To just log and then continue.
+ */
+ static public function factory($name, $value, $params = array()) {
+
+ $className = self::getClassNameFromPropertyName($name);
+ $fileName = str_replace("_", DIRECTORY_SEPARATOR, $className) . ".php";
+ try {
+ qCal_Loader::loadFile($fileName);
+ $class = new $className($value, $params);
+ } catch (qCal_Exception_InvalidFile $e) {
+ // if there is no class available for this property, check if it is non-standard
+ $xname = strtoupper(substr($name, 0, 2));
+ // non-standard property
+ if ($xname == "X-") {
+ $class = new qCal_Property_NonStandard($value, $params, $name);
+ } else {
+ // if it's not a non-standard property, rethrow
+ throw $e;
+ }
+ }
+ return $class;
+
+ }
+ /**
+ * Returns the property name (formatted and exactly to spec)
+ * @return string
+ */
+ public function getName() {
+
+ return $this->name;
+
+ }
+ /**
+ * Returns the property value (as a string)
+ * If you want the actual object, use getValueObject()
+ * I wish I could just pass the object back and have php do some overloading magicness, but
+ * it doesn't know how :(
+ * @return string
+ */
+ public function getValue() {
+
+ return $this->value->__toString();
+
+ }
+ /**
+ * Just returns getValue()
+ */
+ public function __toString() {
+
+ return $this->getValue();
+
+ }
+ /**
+ * Returns raw value object (or for multi-value, an array)
+ * @return string
+ */
+ public function getValueObject() {
+
+ return $this->value;
+
+ }
+ /**
+ * Sets the property value
+ * @param mixed
+ */
+ public function setValue($value) {
+
+ // if value sent is null and this property doesn't have a default value,
+ // the property can't be created, so throw an invalidpropertyvalue exception
+ if (is_null($value)) {
+ if ($this->default === false) {
+ // this is caught by factory and reported as a conformance error
+ throw new qCal_Exception_InvalidPropertyValue($this->getName() . ' property must have a value');
+ } else {
+ $value = $this->default;
+ }
+ }
+ $this->value = $this->convertValue($value);
+ return $this;
+
+ }
+ /**
+ * Converts a value into whatever internal storage mechanism the property uses
+ */
+ protected function convertValue($value) {
+
+ return qCal_Value::factory($this->getType(), $value);
+
+ }
+ /**
+ * Returns the property type
+ * @return string
+ */
+ public function getType() {
+
+ return $this->type;
+
+ }
+ /**
+ * Check if this is a property of a certain component. Some properties
+ * can only be set on certain Components. This method looks inside this
+ * property's $allowedComponents and returns true if $component is allowed
+ *
+ * @return boolean True if this is a property of $component, false otherwise
+ * @param qCal_Component The component we're evaluating
+ **/
+ public function of(qCal_Component $component) {
+
+ return in_array($component->getName(), $this->allowedComponents);
+
+ }
+ /**
+ * Retreive the value of a parameter
+ *
+ * @return mixed parameter value
+ */
+ public function getParam($name) {
+
+ if (isset($this->params[strtoupper($name)])) {
+ return $this->params[strtoupper($name)];
+ }
+
+ }
+ /**
+ * Returns an array of all params
+ */
+ public function getParams() {
+
+ return $this->params;
+
+ }
+ /**
+ * Set the value of a parameter
+ */
+ public function setParam($name, $value) {
+
+ $name = strtoupper($name);
+ // if value param has been passed in, change the type of this property to its value
+ if ($name == "VALUE") {
+ $value = strtoupper($value);
+ $this->type = $value;
+ }
+ $this->params[$name] = $value;
+ return $this;
+
+ }
+ /**
+ * Determine's this property's name from the class name by adding a dash after
+ * every capital letter and upper-casing
+ *
+ * @return string The RFC property name
+ * @todo This method is flawed. The class name XLvFoo gets converted to X-L-VFOO when
+ * it should be X-LV-FOO
+ **/
+ protected function getPropertyNameFromClassName($classname) {
+
+ // determine the property name by class name
+ $parts = explode("_", $classname);
+ end($parts);
+ // find where capital letters are and insert dash
+ $chars = str_split(current($parts));
+ // make a copy @todo Why make a copy?
+ $newchars = $chars;
+ foreach ($chars as $pos => $char) {
+ // don't add a dash for the first letter
+ if (!$pos) continue;
+ $num = ord($char);
+ // if character is a capital letter
+ if ($num >= 65 && $num <= 90) {
+ // insert dash
+ array_splice($newchars, $pos, 0, '-');
+ }
+ }
+ return strtoupper(implode("", $newchars));
+
+ }
+ /**
+ * Determine's this property's class name from the property name
+ *
+ * @return string The property class name
+ **/
+ protected function getClassNameFromPropertyName($name) {
+
+ // remove dashes, capitalize properly
+ $parts = explode("-", $name);
+ $property = "";
+ foreach ($parts as $part) $property .= trim(ucfirst(strtolower($part)));
+ // get the class, and instantiate
+ $className = "qCal_Property_" . $property;
+ return $className;
+
+ }
+ /**
+ * Is this property allowed to be specified multiple times in a component?
+ * @return boolean
+ */
+ public function allowMultiple() {
+
+ return (boolean) $this->allowMultiple;
+
+ }
+
+}
\ No newline at end of file
diff --git a/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Property/Action.php b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Property/Action.php
new file mode 100644
index 0000000..b29eca7
--- /dev/null
+++ b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Property/Action.php
@@ -0,0 +1,51 @@
+":Jim
+ * Dolittle\, ABC Industries\, +1-919-555-1234
+ *
+ * The following is an example of this property referencing a network
+ * resource, such as a vCard [RFC 2426] object containing the contact
+ * information:
+ *
+ * CONTACT;ALTREP="http://host.com/pdi/jdoe.vcf":Jim
+ * Dolittle\, ABC Industries\, +1-919-555-1234
+ */
+class qCal_Property_Contact extends qCal_Property {
+
+ protected $type = 'TEXT';
+ protected $allowedComponents = array('VEVENT','VTODO','VJOURNAL','VFREEBUSY');
+
+}
\ No newline at end of file
diff --git a/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Property/Created.php b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Property/Created.php
new file mode 100644
index 0000000..b26ecdd
--- /dev/null
+++ b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Property/Created.php
@@ -0,0 +1,45 @@
+value as $value) {
+ $return[] = $value->__toString();
+ }
+ return implode(chr(44), $return);
+
+ }
+ /**
+ * Sets the value of this property. Overwrites any previous values. Use addValue to
+ * add rather than overwrite.
+ * @todo I'm not sure I like how this is done. Eventually I will come back to it.
+ */
+ public function setValue($value) {
+
+ if (!is_array($value)) {
+ $value = array($value);
+ }
+ // parent::setValue($value);
+ $this->value = array();
+ foreach ($value as $val) {
+ $this->value[] = $this->convertValue($val);
+ }
+ return $this;
+
+ }
+ /**
+ * Add a value to the array of values (rather than overwrite)
+ */
+ public function addValue($value) {
+
+ $this->value[] = $this->convertValue($value);
+ return $this;
+
+ }
+
+}
\ No newline at end of file
diff --git a/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Property/NonStandard.php b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Property/NonStandard.php
new file mode 100644
index 0000000..0146a1b
--- /dev/null
+++ b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Property/NonStandard.php
@@ -0,0 +1,70 @@
+name = strtoupper($name);
+
+ }
+
+}
\ No newline at end of file
diff --git a/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Property/Organizer.php b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Property/Organizer.php
new file mode 100644
index 0000000..1bc729d
--- /dev/null
+++ b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Property/Organizer.php
@@ -0,0 +1,89 @@
+
+ *
+ * RELATED-TO:<19960401-080045-4000F192713-0052@host1.com>
+ */
+class qCal_Property_RecurrenceId extends qCal_Property {
+
+ protected $type = 'TEXT';
+ protected $allowedComponents = array('VEVENT','VTODO','VJOURNAL');
+ protected $allowMultiple = true;
+
+}
\ No newline at end of file
diff --git a/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Property/Repeat.php b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Property/Repeat.php
new file mode 100644
index 0000000..c236cbc
--- /dev/null
+++ b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Property/Repeat.php
@@ -0,0 +1,47 @@
+ (1997 9:00 AM EDT)September 2-11
+ *
+ * Daily until December 24, 1997:
+ *
+ * DTSTART;TZID=US-Eastern:19970902T090000
+ * RRULE:FREQ=DAILY;UNTIL=19971224T000000Z
+ *
+ * ==> (1997 9:00 AM EDT)September 2-30;October 1-25
+ * (1997 9:00 AM EST)October 26-31;November 1-30;December 1-23
+ *
+ * Every other day - forever:
+ *
+ * DTSTART;TZID=US-Eastern:19970902T090000
+ * RRULE:FREQ=DAILY;INTERVAL=2
+ * ==> (1997 9:00 AM EDT)September2,4,6,8...24,26,28,30;
+ * October 2,4,6...20,22,24
+ * (1997 9:00 AM EST)October 26,28,30;November 1,3,5,7...25,27,29;
+ * Dec 1,3,...
+ *
+ * Every 10 days, 5 occurrences:
+ *
+ * DTSTART;TZID=US-Eastern:19970902T090000
+ * RRULE:FREQ=DAILY;INTERVAL=10;COUNT=5
+ *
+ * ==> (1997 9:00 AM EDT)September 2,12,22;October 2,12
+ *
+ * Everyday in January, for 3 years:
+ *
+ * DTSTART;TZID=US-Eastern:19980101T090000
+ * RRULE:FREQ=YEARLY;UNTIL=20000131T090000Z;
+ * BYMONTH=1;BYDAY=SU,MO,TU,WE,TH,FR,SA
+ * or
+ * RRULE:FREQ=DAILY;UNTIL=20000131T090000Z;BYMONTH=1
+ *
+ * ==> (1998 9:00 AM EDT)January 1-31
+ * (1999 9:00 AM EDT)January 1-31
+ * (2000 9:00 AM EDT)January 1-31
+ *
+ * Weekly for 10 occurrences
+ *
+ * DTSTART;TZID=US-Eastern:19970902T090000
+ * RRULE:FREQ=WEEKLY;COUNT=10
+ *
+ * ==> (1997 9:00 AM EDT)September 2,9,16,23,30;October 7,14,21
+ * (1997 9:00 AM EST)October 28;November 4
+ *
+ * Weekly until December 24, 1997
+ *
+ * DTSTART;TZID=US-Eastern:19970902T090000
+ * RRULE:FREQ=WEEKLY;UNTIL=19971224T000000Z
+ *
+ * ==> (1997 9:00 AM EDT)September 2,9,16,23,30;October 7,14,21
+ * (1997 9:00 AM EST)October 28;November 4,11,18,25;
+ * December 2,9,16,23
+ * Every other week - forever:
+ *
+ * DTSTART;TZID=US-Eastern:19970902T090000
+ * RRULE:FREQ=WEEKLY;INTERVAL=2;WKST=SU
+ *
+ * ==> (1997 9:00 AM EDT)September 2,16,30;October 14
+ * (1997 9:00 AM EST)October 28;November 11,25;December 9,23
+ * (1998 9:00 AM EST)January 6,20;February
+ * ...
+ *
+ * Weekly on Tuesday and Thursday for 5 weeks:
+ *
+ * DTSTART;TZID=US-Eastern:19970902T090000
+ * RRULE:FREQ=WEEKLY;UNTIL=19971007T000000Z;WKST=SU;BYDAY=TU,TH
+ * or
+ * RRULE:FREQ=WEEKLY;COUNT=10;WKST=SU;BYDAY=TU,TH
+ *
+ * ==> (1997 9:00 AM EDT)September 2,4,9,11,16,18,23,25,30;October 2
+ *
+ * Every other week on Monday, Wednesday and Friday until December 24,
+ * 1997, but starting on Tuesday, September 2, 1997:
+ *
+ * DTSTART;TZID=US-Eastern:19970902T090000
+ * RRULE:FREQ=WEEKLY;INTERVAL=2;UNTIL=19971224T000000Z;WKST=SU;
+ * BYDAY=MO,WE,FR
+ * ==> (1997 9:00 AM EDT)September 2,3,5,15,17,19,29;October
+ * 1,3,13,15,17
+ * (1997 9:00 AM EST)October 27,29,31;November 10,12,14,24,26,28;
+ * December 8,10,12,22
+ *
+ * Every other week on Tuesday and Thursday, for 8 occurrences:
+ *
+ * DTSTART;TZID=US-Eastern:19970902T090000
+ * RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=8;WKST=SU;BYDAY=TU,TH
+ *
+ * ==> (1997 9:00 AM EDT)September 2,4,16,18,30;October 2,14,16
+ *
+ * Monthly on the 1st Friday for ten occurrences:
+ *
+ * DTSTART;TZID=US-Eastern:19970905T090000
+ * RRULE:FREQ=MONTHLY;COUNT=10;BYDAY=1FR
+ *
+ * ==> (1997 9:00 AM EDT)September 5;October 3
+ * (1997 9:00 AM EST)November 7;Dec 5
+ * (1998 9:00 AM EST)January 2;February 6;March 6;April 3
+ * (1998 9:00 AM EDT)May 1;June 5
+ *
+ * Monthly on the 1st Friday until December 24, 1997:
+ *
+ * DTSTART;TZID=US-Eastern:19970905T090000
+ * RRULE:FREQ=MONTHLY;UNTIL=19971224T000000Z;BYDAY=1FR
+ *
+ * ==> (1997 9:00 AM EDT)September 5;October 3
+ * (1997 9:00 AM EST)November 7;December 5
+ *
+ * Every other month on the 1st and last Sunday of the month for 10
+ * occurrences:
+ *
+ * DTSTART;TZID=US-Eastern:19970907T090000
+ * RRULE:FREQ=MONTHLY;INTERVAL=2;COUNT=10;BYDAY=1SU,-1SU
+ *
+ * ==> (1997 9:00 AM EDT)September 7,28
+ * (1997 9:00 AM EST)November 2,30
+ * (1998 9:00 AM EST)January 4,25;March 1,29
+ * (1998 9:00 AM EDT)May 3,31
+ *
+ * Monthly on the second to last Monday of the month for 6 months:
+ *
+ * DTSTART;TZID=US-Eastern:19970922T090000
+ * RRULE:FREQ=MONTHLY;COUNT=6;BYDAY=-2MO
+ *
+ * ==> (1997 9:00 AM EDT)September 22;October 20
+ * (1997 9:00 AM EST)November 17;December 22
+ * (1998 9:00 AM EST)January 19;February 16
+ *
+ * Monthly on the third to the last day of the month, forever:
+ *
+ * DTSTART;TZID=US-Eastern:19970928T090000
+ * RRULE:FREQ=MONTHLY;BYMONTHDAY=-3
+ *
+ * ==> (1997 9:00 AM EDT)September 28
+ * (1997 9:00 AM EST)October 29;November 28;December 29
+ * (1998 9:00 AM EST)January 29;February 26
+ * ...
+ *
+ * Monthly on the 2nd and 15th of the month for 10 occurrences:
+ *
+ * DTSTART;TZID=US-Eastern:19970902T090000
+ * RRULE:FREQ=MONTHLY;COUNT=10;BYMONTHDAY=2,15
+ *
+ * ==> (1997 9:00 AM EDT)September 2,15;October 2,15
+ * (1997 9:00 AM EST)November 2,15;December 2,15
+ * (1998 9:00 AM EST)January 2,15
+ *
+ * Monthly on the first and last day of the month for 10 occurrences:
+ *
+ * DTSTART;TZID=US-Eastern:19970930T090000
+ * RRULE:FREQ=MONTHLY;COUNT=10;BYMONTHDAY=1,-1
+ *
+ * ==> (1997 9:00 AM EDT)September 30;October 1
+ * (1997 9:00 AM EST)October 31;November 1,30;December 1,31
+ * (1998 9:00 AM EST)January 1,31;February 1
+ *
+ * Every 18 months on the 10th thru 15th of the month for 10
+ * occurrences:
+ *
+ * DTSTART;TZID=US-Eastern:19970910T090000
+ * RRULE:FREQ=MONTHLY;INTERVAL=18;COUNT=10;BYMONTHDAY=10,11,12,13,14,
+ * 15
+ *
+ * ==> (1997 9:00 AM EDT)September 10,11,12,13,14,15
+ * (1999 9:00 AM EST)March 10,11,12,13
+ *
+ * Every Tuesday, every other month:
+ *
+ * DTSTART;TZID=US-Eastern:19970902T090000
+ * RRULE:FREQ=MONTHLY;INTERVAL=2;BYDAY=TU
+ *
+ * ==> (1997 9:00 AM EDT)September 2,9,16,23,30
+ * (1997 9:00 AM EST)November 4,11,18,25
+ * (1998 9:00 AM EST)January 6,13,20,27;March 3,10,17,24,31
+ * ...
+ *
+ * Yearly in June and July for 10 occurrences:
+ *
+ * DTSTART;TZID=US-Eastern:19970610T090000
+ * RRULE:FREQ=YEARLY;COUNT=10;BYMONTH=6,7
+ * ==> (1997 9:00 AM EDT)June 10;July 10
+ * (1998 9:00 AM EDT)June 10;July 10
+ * (1999 9:00 AM EDT)June 10;July 10
+ * (2000 9:00 AM EDT)June 10;July 10
+ * (2001 9:00 AM EDT)June 10;July 10
+ * Note: Since none of the BYDAY, BYMONTHDAY or BYYEARDAY components
+ * are specified, the day is gotten from DTSTART
+ *
+ * Every other year on January, February, and March for 10 occurrences:
+ *
+ * DTSTART;TZID=US-Eastern:19970310T090000
+ * RRULE:FREQ=YEARLY;INTERVAL=2;COUNT=10;BYMONTH=1,2,3
+ *
+ * ==> (1997 9:00 AM EST)March 10
+ * (1999 9:00 AM EST)January 10;February 10;March 10
+ * (2001 9:00 AM EST)January 10;February 10;March 10
+ * (2003 9:00 AM EST)January 10;February 10;March 10
+ *
+ * Every 3rd year on the 1st, 100th and 200th day for 10 occurrences:
+ *
+ * DTSTART;TZID=US-Eastern:19970101T090000
+ * RRULE:FREQ=YEARLY;INTERVAL=3;COUNT=10;BYYEARDAY=1,100,200
+ *
+ * ==> (1997 9:00 AM EST)January 1
+ * (1997 9:00 AM EDT)April 10;July 19
+ * (2000 9:00 AM EST)January 1
+ * (2000 9:00 AM EDT)April 9;July 18
+ * (2003 9:00 AM EST)January 1
+ * (2003 9:00 AM EDT)April 10;July 19
+ * (2006 9:00 AM EST)January 1
+ *
+ * Every 20th Monday of the year, forever:
+ * DTSTART;TZID=US-Eastern:19970519T090000
+ * RRULE:FREQ=YEARLY;BYDAY=20MO
+ *
+ * ==> (1997 9:00 AM EDT)May 19
+ * (1998 9:00 AM EDT)May 18
+ * (1999 9:00 AM EDT)May 17
+ * ...
+ *
+ * Monday of week number 20 (where the default start of the week is
+ * Monday), forever:
+ *
+ * DTSTART;TZID=US-Eastern:19970512T090000
+ * RRULE:FREQ=YEARLY;BYWEEKNO=20;BYDAY=MO
+ *
+ * ==> (1997 9:00 AM EDT)May 12
+ * (1998 9:00 AM EDT)May 11
+ * (1999 9:00 AM EDT)May 17
+ * ...
+ *
+ * Every Thursday in March, forever:
+ *
+ * DTSTART;TZID=US-Eastern:19970313T090000
+ * RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=TH
+ *
+ * ==> (1997 9:00 AM EST)March 13,20,27
+ * (1998 9:00 AM EST)March 5,12,19,26
+ * (1999 9:00 AM EST)March 4,11,18,25
+ * ...
+ *
+ * Every Thursday, but only during June, July, and August, forever:
+ *
+ * DTSTART;TZID=US-Eastern:19970605T090000
+ * RRULE:FREQ=YEARLY;BYDAY=TH;BYMONTH=6,7,8
+ *
+ * ==> (1997 9:00 AM EDT)June 5,12,19,26;July 3,10,17,24,31;
+ * August 7,14,21,28
+ * (1998 9:00 AM EDT)June 4,11,18,25;July 2,9,16,23,30;
+ * August 6,13,20,27
+ * (1999 9:00 AM EDT)June 3,10,17,24;July 1,8,15,22,29;
+ * August 5,12,19,26
+ * ...
+ *
+ * Every Friday the 13th, forever:
+ *
+ * DTSTART;TZID=US-Eastern:19970902T090000
+ * EXDATE;TZID=US-Eastern:19970902T090000
+ * RRULE:FREQ=MONTHLY;BYDAY=FR;BYMONTHDAY=13
+ *
+ * ==> (1998 9:00 AM EST)February 13;March 13;November 13
+ * (1999 9:00 AM EDT)August 13
+ * (2000 9:00 AM EDT)October 13
+ * ...
+ *
+ * The first Saturday that follows the first Sunday of the month,
+ * forever:
+ *
+ * DTSTART;TZID=US-Eastern:19970913T090000
+ * RRULE:FREQ=MONTHLY;BYDAY=SA;BYMONTHDAY=7,8,9,10,11,12,13
+ *
+ * ==> (1997 9:00 AM EDT)September 13;October 11
+ * (1997 9:00 AM EST)November 8;December 13
+ * (1998 9:00 AM EST)January 10;February 7;March 7
+ * (1998 9:00 AM EDT)April 11;May 9;June 13...
+ * ...
+ *
+ * Every four years, the first Tuesday after a Monday in November,
+ * forever (U.S. Presidential Election day):
+ *
+ * DTSTART;TZID=US-Eastern:19961105T090000
+ * RRULE:FREQ=YEARLY;INTERVAL=4;BYMONTH=11;BYDAY=TU;BYMONTHDAY=2,3,4,
+ * 5,6,7,8
+ *
+ * ==> (1996 9:00 AM EST)November 5
+ * (2000 9:00 AM EST)November 7
+ * (2004 9:00 AM EST)November 2
+ * ...
+ *
+ * The 3rd instance into the month of one of Tuesday, Wednesday or
+ * Thursday, for the next 3 months:
+ *
+ * DTSTART;TZID=US-Eastern:19970904T090000
+ * RRULE:FREQ=MONTHLY;COUNT=3;BYDAY=TU,WE,TH;BYSETPOS=3
+ *
+ * ==> (1997 9:00 AM EDT)September 4;October 7
+ * (1997 9:00 AM EST)November 6
+ *
+ * The 2nd to last weekday of the month:
+ *
+ * DTSTART;TZID=US-Eastern:19970929T090000
+ * RRULE:FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-2
+ *
+ * ==> (1997 9:00 AM EDT)September 29
+ * (1997 9:00 AM EST)October 30;November 27;December 30
+ * (1998 9:00 AM EST)January 29;February 26;March 30
+ * ...
+ *
+ * Every 3 hours from 9:00 AM to 5:00 PM on a specific day:
+ *
+ * DTSTART;TZID=US-Eastern:19970902T090000
+ * RRULE:FREQ=HOURLY;INTERVAL=3;UNTIL=19970902T170000Z
+ *
+ * ==> (September 2, 1997 EDT)09:00,12:00,15:00
+ *
+ * Every 15 minutes for 6 occurrences:
+ *
+ * DTSTART;TZID=US-Eastern:19970902T090000
+ * RRULE:FREQ=MINUTELY;INTERVAL=15;COUNT=6
+ *
+ * ==> (September 2, 1997 EDT)09:00,09:15,09:30,09:45,10:00,10:15
+ *
+ * Every hour and a half for 4 occurrences:
+ *
+ * DTSTART;TZID=US-Eastern:19970902T090000
+ * RRULE:FREQ=MINUTELY;INTERVAL=90;COUNT=4
+ *
+ * ==> (September 2, 1997 EDT)09:00,10:30;12:00;13:30
+ *
+ * Every 20 minutes from 9:00 AM to 4:40 PM every day:
+ *
+ * DTSTART;TZID=US-Eastern:19970902T090000
+ * RRULE:FREQ=DAILY;BYHOUR=9,10,11,12,13,14,15,16;BYMINUTE=0,20,40
+ * or
+ * RRULE:FREQ=MINUTELY;INTERVAL=20;BYHOUR=9,10,11,12,13,14,15,16
+ *
+ * ==> (September 2, 1997 EDT)9:00,9:20,9:40,10:00,10:20,
+ * ... 16:00,16:20,16:40
+ * (September 3, 1997 EDT)9:00,9:20,9:40,10:00,10:20,
+ * ...16:00,16:20,16:40
+ * ...
+ *
+ * An example where the days generated makes a difference because of
+ * WKST:
+ *
+ * DTSTART;TZID=US-Eastern:19970805T090000
+ * RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=MO
+ *
+ * ==> (1997 EDT)Aug 5,10,19,24
+ *
+ * changing only WKST from MO to SU, yields different results...
+ *
+ * DTSTART;TZID=US-Eastern:19970805T090000
+ * RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=SU
+ * ==> (1997 EDT)August 5,17,19,31
+ */
+class qCal_Property_Rrule extends qCal_Property {
+
+ protected $type = 'RECUR';
+ protected $allowedComponents = array('VEVENT','VTODO','VJOURNAL','VTIMEZONE','DAYLIGHT','STANDARD');
+ protected $allowMultiple = true;
+
+}
\ No newline at end of file
diff --git a/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Property/Sequence.php b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Property/Sequence.php
new file mode 100644
index 0000000..53c0954
--- /dev/null
+++ b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Property/Sequence.php
@@ -0,0 +1,94 @@
+
+ * ;Minimum iCalendar version needed to parse the iCalendar object
+ *
+ * maxver =
+ * ;Maximum iCalendar version needed to parse the iCalendar object
+ *
+ * Example: The following is an example of this property:
+ *
+ * VERSION:2.0
+ */
+class qCal_Property_Version extends qCal_Property {
+
+ protected $type = 'TEXT';
+ protected $allowedComponents = array('VCALENDAR');
+ protected $default = "2.0";
+
+}
\ No newline at end of file
diff --git a/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Renderer.php b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Renderer.php
new file mode 100644
index 0000000..e6d229f
--- /dev/null
+++ b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Renderer.php
@@ -0,0 +1,6 @@
+getName() . self::LINE_ENDING;
+ foreach ($component->getProperties() as $property) {
+ if (is_array($property)) {
+ foreach ($property as $prop) {
+ $return .= $this->renderProperty($prop);
+ }
+ } else {
+ $return .= $this->renderProperty($property);
+ }
+ }
+ foreach ($component->getChildren() as $children) {
+ if (is_array($children)) {
+ foreach ($children as $child) {
+ $return .= $this->render($child);
+ }
+ } else {
+ $return .= $this->render($children);
+ }
+ }
+ return $return . "END:" . $component->getName() . self::LINE_ENDING;
+
+ }
+ /**
+ * Renders a property in accordance with rfc 2445
+ * @todo $proptype is created below and never used... wtf?
+ */
+ protected function renderProperty(qCal_Property $property) {
+
+ $propval = $property->getValue();
+ $params = $property->getParams();
+ $paramreturn = "";
+ foreach ($params as $paramname => $paramval) {
+ $paramreturn .= $this->renderParam($paramname, $paramval);
+ }
+ // if property has a "value" param, then use it as the type instead
+ $proptype = isset($params['VALUE']) ? $params['VALUE'] : $property->getType();
+ if ($property instanceof qCal_Property_MultiValue) {
+ $values = array();
+ foreach ($property->getValue() as $value) {
+ $values[] = $this->renderValue($property->getValue(), $proptype);
+ }
+ $value = implode(chr(44), $values);
+ } else {
+ $value = $this->renderValue($property->getValue(), $proptype);
+ }
+ $content = $property->getName() . $paramreturn . ":" . $value . self::LINE_ENDING;
+ return $this->fold($content);
+
+ }
+ /**
+ * Renders a value
+ */
+ protected function renderValue($value, $type) {
+
+ switch(strtoupper($type)) {
+ case "TEXT":
+ $value = str_replace(",", "\,", $value);
+ break;
+ }
+ return $value;
+
+ }
+ /**
+ * Renders a parameter
+ * RFC 2445 says if paramval contains COLON (US-ASCII decimal
+ * 58), SEMICOLON (US-ASCII decimal 59) or COMMA (US-ASCII decimal 44)
+ * character separators MUST be specified as quoted-string text values
+ */
+ protected function renderParam($name, $value) {
+
+ $invchars = array(chr(58),chr(59),chr(44));
+ $quote = false;
+ foreach ($invchars as $char) {
+ if (strstr($value, $char)) {
+ $quote = true;
+ break;
+ }
+ }
+ if ($quote) $value = '"' . $value . '"';
+ return ";" . $name . "=" . $value;
+
+ }
+
+ /**
+ * Text cannot exceed 75 octets. This method will "fold" long lines in accordance with RFC 2445
+ * @todo Make sure this is multi-byte safe
+ * @todo The file I downloaded from google used this same folding method (long lines went to 76)
+ * so until I see any different, I'm going to keep it at 76.
+ */
+ protected function fold($data) {
+
+ if (strlen($data) == (self::FOLD_LENGTH + strlen(self::LINE_ENDING))) return $data;
+ $apart = str_split($data, self::FOLD_LENGTH);
+ return implode(self::LINE_ENDING . " ", $apart);
+
+ }
+
+}
\ No newline at end of file
diff --git a/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Time.php b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Time.php
new file mode 100644
index 0000000..0a4be94
--- /dev/null
+++ b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Time.php
@@ -0,0 +1,198 @@
+setTimezone($timezone)
+ ->setTime($hour, $minute, $second, $rollover);
+
+ }
+ /**
+ * Set the time
+ * @access protected This class is immutable, so this is protected. Only the constructor calls it.
+ */
+ protected function setTime($hour = null, $minute = null, $second = null, $rollover = null) {
+
+ if (is_null($hour)) {
+ $hour = gmdate("H");
+ }
+ if (is_null($minute)) {
+ $minute = gmdate("i");
+ }
+ if (is_null($second)) {
+ $second = gmdate("s");
+ }
+ if (is_null($rollover)) $rollover = false;
+ if (!$rollover) {
+ if ($hour > 23 || $minute > 59 || $second > 59) {
+ throw new qCal_DateTime_Exception_InvalidTime(sprintf("Invalid time specified for qCal_Time: \"%02d:%02d:%02d\"", $hour, $minute, $second));
+ }
+ }
+ // since PHP is incapable of storing a time without a date, we use the first day of
+ // the unix epoch so that we only have the amount of seconds since the zero of unix epoch
+ // we only use gm here because we don't want the server's timezone to interfere
+ $time = gmmktime($hour, $minute, $second, 1, 1, 1970);
+ $this->time = $time;
+ $formatString = "a|A|B|g|G|h|H|i|s|u";
+ $keys = explode("|", $formatString);
+ $vals = explode("|", gmdate($formatString, $this->getTimestamp(false)));
+ $this->timeArray = array_merge($this->timeArray, array_combine($keys, $vals));
+ return $this;
+
+ }
+ /**
+ * Set the timezone
+ */
+ protected function setTimezone($timezone) {
+
+ if (is_null($timezone) || !($timezone instanceof qCal_Timezone)) {
+ $timezone = qCal_Timezone::factory($timezone);
+ }
+ $this->timezone = $timezone;
+ return $this;
+
+ }
+ /**
+ * Get the timezone
+ */
+ public function getTimezone() {
+
+ return $this->timezone;
+
+ }
+ /**
+ * Generate a qCal_Time object via a string or a number of other methods
+ */
+ public static function factory($time, $timezone = null) {
+
+ if (is_null($timezone) || !($timezone instanceof qCal_Timezone)) {
+ $timezone = qCal_Timezone::factory($timezone);
+ }
+ // get the default timezone so we can set it back to it later
+ $tz = date_default_timezone_get();
+ // set the timezone to GMT temporarily
+ date_default_timezone_set("GMT");
+
+ if (is_integer($time)) {
+ // @todo Handle timestamps
+ // @maybe not...
+ }
+ if (is_string($time)) {
+ if ($time == "now") {
+ $time = new qCal_Time(null, null, null, $timezone);
+ } else {
+ $tstring = "01/01/1970 $time";
+ if (!$timestamp = strtotime($tstring)) {
+ // if unix timestamp can't be created throw an exception
+ throw new qCal_DateTime_Exception_InvalidTime("Invalid or ambiguous time string passed to qCal_Time::factory()");
+ }
+ list($hour, $minute, $second) = explode(":", gmdate("H:i:s", $timestamp));
+ $time = new qCal_Time($hour, $minute, $second, $timezone);
+ }
+ }
+
+ // set the timezone back to what it was
+ date_default_timezone_set($tz);
+
+ return $time;
+
+ }
+ /**
+ * Get the hour
+ */
+ public function getHour() {
+
+ return $this->timeArray['G'];
+
+ }
+ /**
+ * Get the minute
+ */
+ public function getMinute() {
+
+ return $this->timeArray['i'];
+
+ }
+ /**
+ * Get the second
+ */
+ public function getSecond() {
+
+ return $this->timeArray['s'];
+
+ }
+ /**
+ * Get the timestamp
+ */
+ public function getTimestamp($useOffset = true) {
+
+ $time = ($useOffset) ?
+ $this->time - $this->getTimezone()->getOffsetSeconds() :
+ $this->time;
+ return $time;
+
+ }
+ /**
+ * Set the format to use when outputting as a string
+ */
+ public function setFormat($format) {
+
+ $this->format = (string) $format;
+ return $this;
+
+ }
+ /**
+ * Output the object using PHP's date() function's meta-characters
+ */
+ public function format($format) {
+
+ $escape = false;
+ $meta = str_split($format);
+ $output = array();
+ foreach($meta as $char) {
+ if ($char == '\\') {
+ $escape = true;
+ continue;
+ }
+ if (!$escape && array_key_exists($char, $this->timeArray)) {
+ $output[] = $this->timeArray[$char];
+ } else {
+ $output[] = $char;
+ }
+ // reset this to false after every iteration that wasn't "continued"
+ $escape = false;
+ }
+ return implode($output);
+
+ }
+ /**
+ * Output the object as a string
+ */
+ public function __toString() {
+
+ return $this->format($this->format);
+
+ }
+
+}
\ No newline at end of file
diff --git a/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Timezone.php b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Timezone.php
new file mode 100644
index 0000000..a1d9c22
--- /dev/null
+++ b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Timezone.php
@@ -0,0 +1,224 @@
+setName($name);
+ $this->setOffsetSeconds($offset);
+ if (is_null($abbreviation)) $abbreviation = $name;
+ $this->setAbbreviation($abbreviation);
+ $this->setIsDaylightSavings($daylightsavings);
+ $this->formatArray = array(
+ 'e' => $this->getName(),
+ 'I' => (integer) $this->isDaylightSavings(),
+ 'O' => $this->getOffsetHours(),
+ 'P' => $this->getOffset(),
+ 'T' => $this->getAbbreviation(),
+ 'Z' => $this->getOffsetSeconds(),
+ );
+
+ }
+ public function setName($name) {
+
+ $this->name = (string) $name;
+
+ }
+ public function setOffsetSeconds($offset) {
+
+ $this->offsetSeconds = (integer) $offset;
+
+ }
+ public function setAbbreviation($abbreviation) {
+
+ $this->abbreviation = (string) $abbreviation;
+
+ }
+ public function setIsDaylightSavings($daylightSavings = null) {
+
+ $this->isDaylightSavings = (boolean) $daylightSavings;
+
+ }
+ /**
+ * Generate a timezone from either an array of parameters, or a timezone
+ * name such as "America/Los_Angeles".
+ * @link http://php.net/manual/en/timezones.php A directory of valid timezones
+ * @todo This method is FUGLY. Rewrite it and make it make sense. This is sort of nonsensical.
+ */
+ public static function factory($timezone = null) {
+
+ if (is_array($timezone)) {
+ // remove anything irrelevant
+ $vals = array_intersect_key($timezone, array_flip(array('name','offsetSeconds','abbreviation','isDaylightSavings')));
+ if (!array_key_exists("name", $vals)) {
+ // @todo throw an exception or something
+ }
+ if (!array_key_exists("offsetSeconds", $vals)) {
+ // @todo throw an exception or something
+ }
+ $name = $vals['name'];
+ $offsetSeconds = $vals['offsetSeconds'];
+ $abbreviation = (array_key_exists('abbreviation', $vals)) ? $vals['abbreviation'] : null;
+ $isDaylightSavings = (array_key_exists('isDaylightSavings', $vals)) ? $vals['isDaylightSavings'] : null;
+ $timezone = new qCal_Timezone($name, $offsetSeconds, $abbreviation, $isDaylightSavings);
+ } else {
+ // get the timezone information out of the string
+ $defaultTz = date_default_timezone_get();
+
+ if (is_null($timezone)) $timezone = $defaultTz;
+
+ // if the timezone being set is invalid, we will get a PHP notice, so error is suppressed here
+ // @todo It would be more clean and probably more efficient to use php's error handling to throw an exception here...
+ if (is_string($timezone)) {
+ @date_default_timezone_set($timezone);
+ // if the function above didn't work, this will be true
+ if (date_default_timezone_get() != $timezone) {
+ // if the timezone requested is registered, use it
+ if (array_key_exists($timezone, self::$timezones)) {
+ $timezone = self::$timezones[$timezone];
+ } else {
+ // otherwise, throw an exception
+ throw new qCal_DateTime_Exception_InvalidTimezone("'$timezone' is not a valid timezone.");
+ }
+ } else {
+ // if the timezone specified was a valid (native php) timezone, use it
+ $name = date("e");
+ $offset = date("Z");
+ $abbr = date("T");
+ $ds = date("I");
+ $timezone = new qCal_Timezone($name, $offset, $abbr, $ds);
+ }
+ }
+
+ // now set it back to what it was...
+ date_default_timezone_set($defaultTz);
+ }
+ return $timezone;
+
+ }
+
+ public static function register(qCal_Timezone $timezone) {
+
+ self::$timezones[$timezone->getName()] = $timezone;
+
+ }
+
+ public static function unregister($timezone) {
+
+ unset(self::$timezones[(string) $timezone]);
+
+ }
+
+ public function getName() {
+
+ return $this->name;
+
+ }
+
+ public function getOffset() {
+
+ $seconds = $this->getOffsetSeconds();
+ $negpos = "+";
+ if ($seconds < 0) {
+ $negpos = "-";
+ }
+ $hours = (integer) ($seconds / 60 / 60);
+ $minutes = $hours * 60;
+ $minutes = ($seconds / 60) - $minutes;
+ return sprintf("%s%02d:%02d", $negpos, abs($hours), abs($minutes));
+
+ }
+
+ public function getOffsetHours() {
+
+ $seconds = $this->getOffsetSeconds();
+ $negpos = "+";
+ if ($seconds < 0) {
+ $negpos = "-";
+ }
+ $hours = (integer) ($seconds / 60 / 60);
+ $minutes = $hours * 60;
+ $minutes = ($seconds / 60) - $minutes;
+ return sprintf("%s%02d%02d", $negpos, abs($hours), abs($minutes));
+
+ }
+
+ public function getOffsetSeconds() {
+
+ return $this->offsetSeconds;
+
+ }
+
+ public function getAbbreviation() {
+
+ return $this->abbreviation;
+
+ }
+
+ public function isDaylightSavings() {
+
+ return $this->isDaylightSavings;
+
+ }
+
+ /**
+ * Set the format that should be used when calling either __toString() or format() without an argument.
+ * @param string $format
+ */
+ public function setFormat($format) {
+
+ $this->format = (string) $format;
+ return $this;
+
+ }
+
+ public function format($format) {
+
+ $escape = false;
+ $meta = str_split($format);
+ $output = array();
+ foreach($meta as $char) {
+ if ($char == '\\') {
+ $escape = true;
+ continue;
+ }
+ if (!$escape && array_key_exists($char, $this->formatArray)) {
+ $output[] = $this->formatArray[$char];
+ } else {
+ $output[] = $char;
+ }
+ // reset this to false after every iteration that wasn't "continued"
+ $escape = false;
+ }
+ return implode($output);
+
+ }
+
+ public function __toString() {
+
+ return $this->format($this->format);
+
+ }
+
+}
\ No newline at end of file
diff --git a/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Value.php b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Value.php
new file mode 100644
index 0000000..6eab2df
--- /dev/null
+++ b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Value.php
@@ -0,0 +1,98 @@
+setValue($value);
+
+ }
+ /**
+ * A factory for data type objects. Pass in a type and a value, and it will return the value
+ * casted to the proper type
+ */
+ public static function factory($type, $value) {
+
+ // remove dashes, capitalize properly
+ $parts = explode("-", $type);
+ $type = "";
+ foreach ($parts as $part) $type .= trim(ucfirst(strtolower($part)));
+ // get the class, and instantiate
+ $className = "qCal_Value_" . $type;
+ $class = new $className($value);
+ return $class;
+
+ }
+ /**
+ * Sets the value of this object. The beauty of using inheritence here is that I can store
+ * the value however I want for any value type, and then on __toString() I can return it how
+ * iCalendar specifies :)
+ */
+ public function setValue($value) {
+
+ $this->value = $this->doCast($value);
+ return $this;
+
+ }
+ /**
+ * Returns raw value (as it is stored)
+ */
+ public function getValue() {
+
+ return $this->value;
+
+ }
+ /**
+ * Casts $value to this data type
+ */
+ public function cast($value) {
+
+ return $this->doCast($value);
+
+ }
+ /**
+ * Returns the value as a string
+ */
+ public function __toString() {
+
+ return $this->toString($this->value);
+
+ }
+ /**
+ * Converts from native format to a string, __toString() calls this internally
+ */
+ protected function toString($value) {
+
+ return (string) $value;
+
+ }
+ /**
+ * This is left to be implemented by children classes, basically they
+ * implement this method to cast any input into their data type (from a string)
+ * @todo Change the name of this to something more appropriate, maybe toNative or something
+ */
+ abstract protected function doCast($value);
+
+}
\ No newline at end of file
diff --git a/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Value/Binary.php b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Value/Binary.php
new file mode 100644
index 0000000..7354dcb
--- /dev/null
+++ b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Value/Binary.php
@@ -0,0 +1,69 @@
+
+ *
+ * qCal_DataType_Binary
+ * This object defines any binary object that may be attached to an
+ * icalendar file.
+ */
+class qCal_Value_Binary extends qCal_Value {
+
+ /**
+ * When the value of a binary property is requested, it will be returned as a base64 encoded string
+ * @todo Base64 is the only encoding supported by this standard, but the encoding=base64 parameter must be
+ * provided regardless.
+ */
+ protected function toString($value) {
+
+ return base64_encode($value);
+
+ }
+ /**
+ * Binary can be store as-is I believe, so don't change it
+ */
+ protected function doCast($value) {
+
+ return $value;
+
+ }
+
+}
\ No newline at end of file
diff --git a/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Value/Boolean.php b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Value/Boolean.php
new file mode 100644
index 0000000..890b180
--- /dev/null
+++ b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Value/Boolean.php
@@ -0,0 +1,47 @@
+format('Ymd');
+
+ }
+ /**
+ * This converts to a qCal_Date for internal storage
+ */
+ protected function doCast($value) {
+
+ $date = qCal_Date::factory($value);
+ return $date;
+
+ }
+
+}
\ No newline at end of file
diff --git a/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Value/DateTime.php b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Value/DateTime.php
new file mode 100644
index 0000000..01034a9
--- /dev/null
+++ b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Value/DateTime.php
@@ -0,0 +1,134 @@
+format('Ymd\THis');
+
+ }
+ /**
+ * This converts to a qCal_Date for internal storage
+ */
+ protected function doCast($value) {
+
+ // @todo This may be the wrong place to do this...
+ if ($value instanceof qCal_DateTime) {
+ return $value;
+ }
+ $date = qCal_DateTime::factory($value);
+ return $date;
+
+ }
+
+}
\ No newline at end of file
diff --git a/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Value/Duration.php b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Value/Duration.php
new file mode 100644
index 0000000..15ff4e3
--- /dev/null
+++ b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Value/Duration.php
@@ -0,0 +1,67 @@
+toICal();
+
+ }
+ /**
+ * Convert to internal representation
+ */
+ protected function doCast($value) {
+
+ return new qCal_DateTime_Duration($value);
+
+ }
+
+}
\ No newline at end of file
diff --git a/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Value/Float.php b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Value/Float.php
new file mode 100644
index 0000000..5fc5234
--- /dev/null
+++ b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Value/Float.php
@@ -0,0 +1,44 @@
+getUnixTimestamp() + $duration->getSeconds()); // @todo This needs to be updated once qCal_DateTime accepts timestamps
+ }
+ return new qCal_DateTime_Period($start, $end);
+
+ }
+ /**
+ * Convert to string - this converts to string into the UTC/UTC format
+ */
+ protected function toString($value) {
+
+ return $value->getStart()->getUtc() . "/"
+ . $value->getEnd()->getUtc();
+
+ }
+
+}
\ No newline at end of file
diff --git a/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Value/Recur.php b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Value/Recur.php
new file mode 100644
index 0000000..13edea8
--- /dev/null
+++ b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Value/Recur.php
@@ -0,0 +1,279 @@
+format('His');
+
+ }
+ /**
+ * This converts to a qCal_Date for internal storage
+ */
+ protected function doCast($value) {
+
+ $date = qCal_Time::factory($value);
+ return $date;
+
+ }
+
+}
\ No newline at end of file
diff --git a/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Value/Uri.php b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Value/Uri.php
new file mode 100644
index 0000000..ff0812f
--- /dev/null
+++ b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Value/Uri.php
@@ -0,0 +1,56 @@
+
+ *
+ * Description: This data type might be used to reference binary
+ * information, for values that are large, or otherwise undesirable to
+ * include directly in the iCalendar object.
+ *
+ * The URI value formats in RFC 1738, RFC 2111 and any other IETF
+ * registered value format can be specified.
+ *
+ * Any IANA registered URI format can be used. These include, but are
+ * not limited to, those defined in RFC 1738 and RFC 2111.
+ *
+ * When a property parameter value is a URI value type, the URI MUST be
+ * specified as a quoted-string value.
+ *
+ * No additional content value encoding (i.e., BACKSLASH character
+ * encoding) is defined for this value type.
+ *
+ * Example: The following is a URI for a network file:
+ *
+ * http://host1.com/my-report.txt
+ */
+class qCal_Value_Uri extends qCal_Value {
+
+ protected function toString($value) {
+
+ return (string) $value;
+
+ }
+ /**
+ * @todo: implement this
+ */
+ protected function doCast($value) {
+
+ return $value;
+
+ }
+
+}
\ No newline at end of file
diff --git a/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Value/UtcOffset.php b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Value/UtcOffset.php
new file mode 100644
index 0000000..e04eece
--- /dev/null
+++ b/pkg/vtiger/modules/Mobile/modules/Mobile/third-party/qCal/qCal/Value/UtcOffset.php
@@ -0,0 +1,50 @@
+error = $e;
+ }
+
+ function process(Mobile_API_Request $request) {
+ $viewer = new Mobile_UI_Viewer();
+ $viewer->assign('errorcode', $this->error['code']);
+ $viewer->assign('errormsg', $this->error['message']);
+ return $viewer->process('generic/Error.tpl');
+ }
+
+}
\ No newline at end of file
diff --git a/pkg/vtiger/modules/Mobile/modules/Mobile/ui/FetchRecordWithGrouping.php b/pkg/vtiger/modules/Mobile/modules/Mobile/ui/FetchRecordWithGrouping.php
new file mode 100644
index 0000000..c93df37
--- /dev/null
+++ b/pkg/vtiger/modules/Mobile/modules/Mobile/ui/FetchRecordWithGrouping.php
@@ -0,0 +1,46 @@
+sessionGet('_MODULES'); // Should be available post login
+ foreach($modules as $module) {
+ if ($module->id() == $recordIdComponents[0]) { return $module; };
+ }
+ return false;
+ }
+
+ function process(Mobile_API_Request $request) {
+ $wsResponse = parent::process($request);
+
+ $response = false;
+ if($wsResponse->hasError()) {
+ $response = $wsResponse;
+ } else {
+ $wsResponseResult = $wsResponse->getResult();
+
+ $module = $this->cachedModuleLookupWithRecordId($wsResponseResult['record']['id']);
+ $record = Mobile_UI_ModuleRecordModel::buildModelFromResponse($wsResponseResult['record']);
+ $record->setId($wsResponseResult['record']['id']);
+
+ $viewer = new Mobile_UI_Viewer();
+ $viewer->assign('_MODULE', $module);
+ $viewer->assign('_RECORD', $record);
+
+ $response = $viewer->process('generic/Detail.tpl');
+ }
+ return $response;
+ }
+
+}
\ No newline at end of file
diff --git a/pkg/vtiger/modules/Mobile/modules/Mobile/ui/ListModuleRecords.php b/pkg/vtiger/modules/Mobile/modules/Mobile/ui/ListModuleRecords.php
new file mode 100644
index 0000000..9c22dc8
--- /dev/null
+++ b/pkg/vtiger/modules/Mobile/modules/Mobile/ui/ListModuleRecords.php
@@ -0,0 +1,67 @@
+sessionGet('_MODULES'); // Should be available post login
+ foreach($modules as $module) {
+ if ($module->name() == $moduleName) return $module;
+ }
+ return false;
+ }
+
+ function getPagingModel(Mobile_API_Request $request) {
+ $pagingModel = Mobile_WS_PagingModel::modelWithPageStart($request->get('page'));
+ $pagingModel->setLimit(Mobile::config('Navigation.Limit', 100));
+ return $pagingModel;
+ }
+
+ /** For search capability */
+ function cachedSearchFields($module) {
+ $cachekey = "_MODULE.{$module}.SEARCHFIELDS";
+ return $this->sessionGet($cachekey, false);
+ }
+
+ function getSearchFilterModel($module, $search) {
+ $searchFilter = false;
+ if (!empty($search)) {
+ $criterias = array('search' => $search, 'fieldnames' => $this->cachedSearchFields($module));
+ $searchFilter = Mobile_UI_SearchFilterModel::modelWithCriterias($module, $criterias);
+ return $searchFilter;
+ }
+ return $searchFilter;
+ }
+ /** END */
+
+ function process(Mobile_API_Request $request) {
+ $wsResponse = parent::process($request);
+
+ $response = false;
+ if($wsResponse->hasError()) {
+ $response = $wsResponse;
+ } else {
+ $wsResponseResult = $wsResponse->getResult();
+
+ $viewer = new Mobile_UI_Viewer();
+ $viewer->assign('_MODULE', $this->cachedModule($wsResponseResult['module']) );
+ $viewer->assign('_RECORDS', Mobile_UI_ModuleRecordModel::buildModelsFromResponse($wsResponseResult['records']) );
+ $viewer->assign('_MODE', $request->get('mode'));
+ $viewer->assign('_PAGER', $this->getPagingModel($request));
+ $viewer->assign('_SEARCH', $request->get('search'));
+
+ $response = $viewer->process('generic/List.tpl');
+ }
+ return $response;
+ }
+
+}
\ No newline at end of file
diff --git a/pkg/vtiger/modules/Mobile/modules/Mobile/ui/Login.php b/pkg/vtiger/modules/Mobile/modules/Mobile/ui/Login.php
new file mode 100644
index 0000000..8002bd5
--- /dev/null
+++ b/pkg/vtiger/modules/Mobile/modules/Mobile/ui/Login.php
@@ -0,0 +1,20 @@
+process('generic/Login.tpl');
+ }
+
+}
\ No newline at end of file
diff --git a/pkg/vtiger/modules/Mobile/modules/Mobile/ui/LoginAndFetchModules.php b/pkg/vtiger/modules/Mobile/modules/Mobile/ui/LoginAndFetchModules.php
new file mode 100644
index 0000000..3f8c0d5
--- /dev/null
+++ b/pkg/vtiger/modules/Mobile/modules/Mobile/ui/LoginAndFetchModules.php
@@ -0,0 +1,38 @@
+sessionSet("_MODULES", $modules);
+ }
+
+ function process(Mobile_API_Request $request) {
+ $wsResponse = parent::process($request);
+
+ $response = false;
+ if($wsResponse->hasError()) {
+ $response = $wsResponse;
+ } else {
+ $wsResponseResult = $wsResponse->getResult();
+
+ $modules = Mobile_UI_ModuleModel::buildModelsFromResponse($wsResponseResult['modules']);
+ $this->cacheModules($modules);
+
+ $viewer = new Mobile_UI_Viewer();
+ $viewer->assign('_MODULES', $modules);
+
+ $response = $viewer->process('generic/Home.tpl');
+ }
+ return $response;
+ }
+
+}
\ No newline at end of file
diff --git a/pkg/vtiger/modules/Mobile/modules/Mobile/ui/Logout.php b/pkg/vtiger/modules/Mobile/modules/Mobile/ui/Logout.php
new file mode 100644
index 0000000..da958a6
--- /dev/null
+++ b/pkg/vtiger/modules/Mobile/modules/Mobile/ui/Logout.php
@@ -0,0 +1,19 @@
+sessionGet('_MODULES'); // Should be available post login
+ foreach($modules as $module) {
+ if ($module->name() == $moduleName) return $module;
+ }
+ return false;
+ }
+
+ function cacheSearchFields($module, $fieldnames) {
+ $this->sessionSet("_MODULE.{$module}.SEARCHFIELDS", $fieldnames);
+ }
+
+ function cachedSearchFields($module) {
+ $cachekey = "_MODULE.{$module}.SEARCHFIELDS";
+ return $this->sessionGet($cachekey, array());
+ }
+
+ function process(Mobile_API_Request $request) {
+ $mode = $request->get('mode');
+ $module = $this->cachedModule($request->get('module'));
+
+ $searchIn = $this->cachedSearchFields($module->name());
+
+ if($mode == 'update') {
+ $searchIn = array();
+ foreach($_REQUEST as $k=>$v) {
+ if(preg_match("/field_(.*)/i", $k, $m)) {
+ $searchIn[] = vtlib_purify($m[1]);
+ }
+ }
+ $this->cacheSearchFields($module->name(), $searchIn);
+ header("Location: index.php?_operation=listModuleRecords&module={$module->name()}&mode=search");
+ exit;
+ }
+
+ $request->setDefault('record', "{$module->id()}x0");
+
+ $wsResponse = parent::process($request);
+ $wsResponseResult = $wsResponse->getResult();
+
+ $templateRecord = Mobile_UI_ModuleRecordModel::buildModelFromResponse($wsResponseResult['record']);
+
+ $viewer = new Mobile_UI_Viewer();
+ $viewer->assign('_MODULE', $module );
+ $viewer->assign('_RECORD', $templateRecord );
+ $viewer->assign('_SEARCHIN', $searchIn);
+ $viewer->assign('_SEARCHIN_ALL', empty($searchIn));
+
+ $response = $viewer->process('generic/SearchConfig.tpl');
+
+ return $response;
+ }
+}
\ No newline at end of file
diff --git a/pkg/vtiger/modules/Mobile/modules/Mobile/ui/Viewer.php b/pkg/vtiger/modules/Mobile/modules/Mobile/ui/Viewer.php
new file mode 100644
index 0000000..6e4e726
--- /dev/null
+++ b/pkg/vtiger/modules/Mobile/modules/Mobile/ui/Viewer.php
@@ -0,0 +1,39 @@
+parameters[$key] = $value;
+ }
+
+ function viewController() {
+ $smarty = new vtigerCRM_Smarty();
+
+ foreach($this->parameters as $k => $v) {
+ $smarty->assign($k, $v);
+ }
+
+ $smarty->assign("IS_SAFARI", Mobile::isSafari());
+ $smarty->assign("SKIN", Mobile::config('Default.Skin'));
+ return $smarty;
+ }
+
+ function process($templateName) {
+ $smarty = $this->viewController();
+ $response = new Mobile_API_Response();
+ $response->setResult($smarty->fetch(vtlib_getModuleTemplate('Mobile', $templateName)));
+ return $response;
+ }
+
+}
\ No newline at end of file
diff --git a/pkg/vtiger/modules/Mobile/modules/Mobile/ui/models/Block.php b/pkg/vtiger/modules/Mobile/modules/Mobile/ui/models/Block.php
new file mode 100644
index 0000000..0d6f790
--- /dev/null
+++ b/pkg/vtiger/modules/Mobile/modules/Mobile/ui/models/Block.php
@@ -0,0 +1,42 @@
+_label = $blockData['label'];
+ if (isset($blockData['fields'])) {
+ $this->_fields = Mobile_UI_FieldModel::buildModelsFromResponse($blockData['fields']);
+ }
+ }
+
+ function label() {
+ return $this->_label;
+ }
+
+ function fields() {
+ return $this->_fields;
+ }
+
+ static function buildModelsFromResponse($blocks) {
+ $instances = array();
+ foreach($blocks as $blockData) {
+ $instance = new self();
+ $instance->initData($blockData);
+ $instances[] = $instance;
+ }
+ return $instances;
+ }
+
+}
\ No newline at end of file
diff --git a/pkg/vtiger/modules/Mobile/modules/Mobile/ui/models/Field.php b/pkg/vtiger/modules/Mobile/modules/Mobile/ui/models/Field.php
new file mode 100644
index 0000000..5c4a78b
--- /dev/null
+++ b/pkg/vtiger/modules/Mobile/modules/Mobile/ui/models/Field.php
@@ -0,0 +1,79 @@
+data = $fieldData;
+ }
+
+ function name() {
+ return $this->data['name'];
+ }
+
+ function value() {
+ $rawValue = $this->data['value'];
+ if (is_array($rawValue)) return $rawValue['value'];
+ return $rawValue;
+ }
+
+ function valueLabel() {
+ $rawValue = $this->data['value'];
+ if (is_array($rawValue)) return $rawValue['label'];
+ return $rawValue;
+ }
+
+ function label() {
+ return $this->data['label'];
+ }
+
+ function isReferenceType() {
+ static $options = array('101', '116', '117', '26', '357',
+ '50', '51', '52', '53', '57', '58', '59', '66',
+ '73', '75', '76', '77', '78', '80', '81'
+ );
+ if (isset($this->data['uitype'])) {
+ $uitype = $this->data['uitype'];
+ if (in_array($uitype, $options)) {
+ return true;
+ }
+ } else if(isset($this->data['type'])) {
+ switch($this->data['type']['name']) {
+ case 'reference':
+ case 'owner':
+ return true;
+ }
+ }
+ return $this->isMultiReferenceType();
+ }
+
+ function isMultiReferenceType() {
+ static $options = array('10', '68');
+
+ $uitype = $this->data['uitype'];
+ if (in_array($uitype, $options)) {
+ return true;
+ }
+ return false;
+ }
+
+ static function buildModelsFromResponse($fields) {
+ $instances = array();
+
+ foreach($fields as $fieldData) {
+ $instance = new self();
+ $instance->initData($fieldData);
+ $instances[] = $instance;
+ }
+ return $instances;
+ }
+
+}
\ No newline at end of file
diff --git a/pkg/vtiger/modules/Mobile/modules/Mobile/ui/models/Module.php b/pkg/vtiger/modules/Mobile/modules/Mobile/ui/models/Module.php
new file mode 100644
index 0000000..be80b22
--- /dev/null
+++ b/pkg/vtiger/modules/Mobile/modules/Mobile/ui/models/Module.php
@@ -0,0 +1,42 @@
+data = $moduleData;
+ }
+
+ function id() {
+ return $this->data['id'];
+ }
+
+ function name() {
+ return $this->data['name'];
+ }
+
+ function label() {
+ return $this->data['label'];
+ }
+
+ static function buildModelsFromResponse($modules) {
+ $instances = array();
+ foreach($modules as $moduleData) {
+ $instance = new self();
+ $instance->initData($moduleData);
+ $instances[] = $instance;
+ }
+ return $instances;
+ }
+
+}
\ No newline at end of file
diff --git a/pkg/vtiger/modules/Mobile/modules/Mobile/ui/models/ModuleRecord.php b/pkg/vtiger/modules/Mobile/modules/Mobile/ui/models/ModuleRecord.php
new file mode 100644
index 0000000..ac67457
--- /dev/null
+++ b/pkg/vtiger/modules/Mobile/modules/Mobile/ui/models/ModuleRecord.php
@@ -0,0 +1,59 @@
+data = $recordData;
+ if (isset($recordData['blocks'])) {
+ $blocks = Mobile_UI_BlockModel::buildModelsFromResponse($recordData['blocks']);
+ foreach($blocks as $block) {
+ $this->_blocks[$block->label()] = $block;
+ }
+ }
+ }
+
+ function setId($newId) {
+ $this->_id = $newId;
+ }
+
+ function id() {
+ return $this->data['id'];
+ }
+
+ function label() {
+ return $this->data['label'];
+ }
+
+ function blocks() {
+ return $this->_blocks;
+ }
+
+ static function buildModelFromResponse($recordData) {
+ $instance = new self();
+ $instance->initData($recordData);
+ return $instance;
+ }
+
+ static function buildModelsFromResponse($records) {
+ $instances = array();
+ foreach($records as $recordData) {
+ $instance = new self();
+ $instance->initData($recordData);
+ $instances[] = $instance;
+ }
+ return $instances;
+ }
+
+}
\ No newline at end of file
diff --git a/pkg/vtiger/modules/Mobile/modules/Mobile/ui/models/SearchFilter.php b/pkg/vtiger/modules/Mobile/modules/Mobile/ui/models/SearchFilter.php
new file mode 100644
index 0000000..9eaa8d0
--- /dev/null
+++ b/pkg/vtiger/modules/Mobile/modules/Mobile/ui/models/SearchFilter.php
@@ -0,0 +1,74 @@
+criterias['search'];
+ $fieldnames = (isset($this->criterias['fieldnames']))? $this->criterias['fieldnames'] : false;
+
+ include_once 'include/Webservices/DescribeObject.php';
+ $describeInfo = vtws_describe($this->moduleName, $this->getUser());
+
+ $fieldinfos = array();
+ if ($fieldnames === false) {
+ foreach($describeInfo['fields'] as $fieldinfo) {
+ $fieldmodel = new Mobile_UI_FieldModel();
+ $fieldmodel->initData($fieldinfo);
+
+ if (!$fieldmodel->isReferenceType()) {
+ $fieldinfos[$fieldinfo['name']] = $fieldmodel;
+ }
+ }
+
+ } else {
+ foreach($describeInfo['fields'] as $fieldinfo) {
+ if(in_array($fieldinfo['name'], $fieldnames)) {
+ $fieldmodel = new Mobile_UI_FieldModel();
+ $fieldmodel->initData($fieldinfo);
+
+ if (!$fieldmodel->isReferenceType()) {
+ $fieldinfos[$fieldinfo['name']] = $fieldmodel;
+ }
+ }
+ }
+ }
+
+ if(isset($fieldinfos['id'])) unset($fieldinfos['id']);
+ if(!empty($fieldinfos)) {
+ $fieldinfos['_'] = ''; // Hack to build the where clause at once
+ $whereClause = sprintf("WHERE %s", implode(" LIKE '%{$searchString}%' OR ", array_keys($fieldinfos)));
+ $whereClause = rtrim($whereClause, 'OR _');
+ }
+
+ return $whereClause;
+ }
+
+ function execute($fieldnames, $pagingModel = false) {
+ $selectClause = sprintf("SELECT %s", implode(',', $fieldnames));
+ $fromClause = sprintf("FROM %s", $this->moduleName);
+ $whereClause = $this->prepareWhereClause(false);
+ $orderClause = "";
+ $groupClause = "";
+ $limitClause = $pagingModel? " LIMIT {$pagingModel->currentCount()},{$pagingModel->limit()}" : "" ;
+
+ $query = sprintf("%s %s %s %s %s %s;", $selectClause, $fromClause, $whereClause, $orderClause, $groupClause, $limitClause);
+ return vtws_query($query, $this->getUser());
+ }
+
+ static function modelWithCriterias($moduleName, $criterias = false) {
+ $model = new Mobile_UI_SearchFilterModel($moduleName);
+ $model->setCriterias($criterias);
+ return $model;
+ }
+}
\ No newline at end of file
diff --git a/pkg/vtiger/modules/Mobile/templates/generic/Detail.tpl b/pkg/vtiger/modules/Mobile/templates/generic/Detail.tpl
new file mode 100644
index 0000000..2ab8ad0
--- /dev/null
+++ b/pkg/vtiger/modules/Mobile/templates/generic/Detail.tpl
@@ -0,0 +1,52 @@
+{include file="modules/Mobile/generic/Header.tpl"}
+
+
+
+
+
+
+
+ |
+ {$_MODULE->label()} |
+
+
+
+ |
+
+
+
+
+
+ {foreach item=_BLOCK key=_BLOCKLABEL from=$_RECORD->blocks()}
+
+ {assign var=_FIELDS value=$_BLOCK->fields()}
+
+ {if !empty($_FIELDS)}
+
+ {$_BLOCKLABEL} |
+
+ {/if}
+
+ {foreach item=_FIELD from=$_FIELDS}
+
+ {$_FIELD->label()} |
+
+ {if $_FIELD->isReferenceType()}
+ {$_FIELD->valueLabel()}
+ {else}
+ {$_FIELD->valueLabel()}
+ {/if}
+ |
+
+ {/foreach}
+
+ {/foreach}
+
+
+ |
+
+
+
+
+
+{include file="modules/Mobile/generic/Footer.tpl"}
\ No newline at end of file
diff --git a/pkg/vtiger/modules/Mobile/templates/generic/Error.tpl b/pkg/vtiger/modules/Mobile/templates/generic/Error.tpl
new file mode 100644
index 0000000..f13d94b
--- /dev/null
+++ b/pkg/vtiger/modules/Mobile/templates/generic/Error.tpl
@@ -0,0 +1,31 @@
+{include file="modules/Mobile/generic/Header.tpl"}
+
+
+
+
+
+
+ vtiger CRM
+ |
+
+
+
+
+
+ |
+
+
+
+
+
+{include file="modules/Mobile/generic/Footer.tpl"}
\ No newline at end of file
diff --git a/pkg/vtiger/modules/Mobile/templates/generic/Footer.tpl b/pkg/vtiger/modules/Mobile/templates/generic/Footer.tpl
new file mode 100644
index 0000000..62d09b8
--- /dev/null
+++ b/pkg/vtiger/modules/Mobile/templates/generic/Footer.tpl
@@ -0,0 +1 @@
+