diff --git a/.gitignore b/.gitignore index 04a50094..30b18a91 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,11 @@ backup/ sys/solr_configsets/default_config/*.unloaded +sys/solr_configsets/default_config/ +!sys/solr_configsets/default_config/conf ssl/ +\[ssl\]/ # ignore custom import scripts install/import/ diff --git a/README.md b/README.md index e3b9fbb7..656d4e91 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,13 @@ Casebox Casebox is a Content Management Platform for record, file and task management. -Full documentation can be found on the website: -http://docs.casebox.org/en/latest/ +Casebox was developed jointly by HURIDOCS and KETSE.com. -Made at Ketse & HURIDOCS -------------------------- +Starting in 2017, HURIDOCS manages its own version of the Casebox codebase for human rights organisations and KETSE.com continues to support the original code. + +If you are working with a human rights organisation, contact casebox at huridocs.org -Casebox is being developed by [Ketse](https://www.ketse.com/) & [HURIDOCS](https://www.huridocs.org/). +If your request is not related to human rights work and you are not a non-profit, then please contact Ketse at info at ketse.com for more information on the commercial services they can provide. Code status diff --git a/bin/import_objects_translations.php b/bin/import_objects_translations.php new file mode 100644 index 00000000..034b088f --- /dev/null +++ b/bin/import_objects_translations.php @@ -0,0 +1,180 @@ +#!/usr/bin/php +"Cheeze","fr"=>"Fromage"] + */ +function updateObjectTranslations ($id, $translations) { + $obj = Objects::getCustomClassByObjectId($id); + if(!$obj) { + printLine("Object #".$id." not found"); + return; + } + // load data from DB first in order not to overwrite/lose + // existing data that's not part of the update + $obj->load(); + $data = $obj->getData(); + foreach($translations as $lg=>$text) { + $data['data']['title_'.$lg] = $text; + } + $obj->update($data); +} + +/** + * reads the next line of the specified csv + * file handle in an attempt to get the languages + * in the translation, this should be used to + * read the first line of the file, before other + * reads are done + * @param handle $file + * @return array array of language codes from the csv + * header row + */ +function parseHeader ($file) { + $header = fgetcsv($file); + $langs = array_map('trim', array_slice($header, 1)); + return $langs; +} + +/** + * reads the next line of the specified csv file + * handle and maps languages to translations of + * the object at that row + * the first column of the row is considered the object + * id and the remaining columns as translations + * the number of columns should therefore be 1 + the + * size of the $langs array + * @param handle $file + * @param array $langs array of language codes + * @return array|null associative array with keys id and + * translations, where translations is an associative + * array mapping languages to translations for the given object id + * returns null if the line is empty + */ +function parseNextTranslations ($file, $langs) { + $row = fgetcsv($file); + if (empty($row)) { + // return null on empty lines + return null; + } + $id = $row[0]; + // clean id if it starts with # character + if($id[0] == '#') { + $id = substr($id, 1); + } + $id = (int) $id; + $values = array_slice($row, 1); + $trans = array_combine($langs, $values); + return [ + "id" => $id, + "translations" => $trans + ]; +} + +/** + * helper to echo logs + * @param string $str + */ +function printLine ($str) { + echo $str."\n"; +} + +/** + * prints usage example and instructions + */ +function printUsage () { + printLine("USAGE:"); + printLine("php import_objects_translations -c corename -f filename"); +} diff --git a/bin/update_fields.php b/bin/update_fields.php new file mode 100644 index 00000000..82f4e007 --- /dev/null +++ b/bin/update_fields.php @@ -0,0 +1,216 @@ +#!/usr/bin/php +updateObjects(); + println('Done'); + println("Now update solr prepared data and reindex the solr core"); +} + + +class FieldUpdater { + + private $templateIds; + private $fields; + + /** + * @param array $fs mapping of old field names to new names + * @param array $ts list of template ids + */ + function __construct ($fs, $ts=null) { + $this->templateIds = $ts; + $this->fields = $fs; + } + + /** + * Fetches and updates objects + * based on the set template ids and field + * names to udpate + */ + public function updateObjects () { + $res = $this->fetchObjectIds(); + while ($row = $res->fetch_assoc()) { + $this->updateObject($row); + } + } + + /** + * update the object with the specified id + * @param array $row db row with id and data fields + */ + public function updateObject ($row) { + $data = json_decode($row['data'], true); + if (empty($data)) { + println("Empty data for object ".$row['id']); + return; + } + foreach ($this->fields as $old=>$new) { + if (array_key_exists($old, $data)) { + $data[$new] = $data[$old]; + unset($data[$old]); + } + } + $this->saveToDb($row['id'], $data); + } + + /** + * fetch object ids from the db + * @return resource the db cursor + */ + private function fetchObjectIds () { + $q = $this->buildQuery(); + return DB\dbQuery($q); + } + + /** + * builds sql query to use for fetching + * objects based on the specified templates + * @return string + */ + private function buildQuery () { + $q = "SELECT o.id, o.data + FROM objects o"; + if (!empty($this->templateIds)) { + $ids = implode(',', $this->templateIds); + $q .= " JOIN tree t on o.id=t.id AND t.template_id in (".$ids.")"; + } + return $q; + } + + /** + * persists object data in db + * @param mixed $id id of the object to update + * @param array $data + */ + private function saveToDb ($id, $data) { + $data = json_encode($data); + $q = "UPDATE objects SET data=$2 + WHERE id=$1"; + DB\dbQuery($q, [$id, $data]); + } +} + +/** + * parses the templates string arg and returns + * an array of template ids + * @param string $t comma separated list of template + * ids: id1,id2,id3 + * @return array + */ +function parseTemplates ($t) { + return explode(',', $t); +} + +/** + * parses the fields string arg and returns + * an array mapping old fields to new fields + * @param string $f string-encoded list old to new fields + * mapping in the form oldName1:newName1,oldName2:newName2 + * @return array associative array mapping old field names + * to new names + */ +function parseFields ($f) { + $pairs = explode(',', $f); + return array_reduce($pairs, function($res, $item) { + $oldNew = explode(':', $item); + $res[$oldNew[0]] = $oldNew[1]; + return $res; + }, []); +} + +/** + * prints helpful usage info + */ +function printUsage() { + println('php update_fields.php -c -f -t '); + println('Options:'); + println('-c : the Casebox core name without the cb_ prefix'); + println('-f : list of fields to updated in the form oldName1:newName1,oldName2:newName2'); + println('-t (optional): comma-separated list of template ids'); + println('Note: If option -t is provided, only objects from those templates will be updated,' + .' otherwise ALL objects will be updated.'); + println(); + println("Example:"); + println("php update_fields.php -c demo -f age:victim_age,sex:gender -t 3849,1234"); +} + +/** + * ouputs the specified string followed by a new line + * @param string $s + */ +function println ($s='') { + echo "$s\n"; +} diff --git a/httpsdocs/classes/CB/Browser.php b/httpsdocs/classes/CB/Browser.php index 30bbdac5..43f5cf0f 100644 --- a/httpsdocs/classes/CB/Browser.php +++ b/httpsdocs/classes/CB/Browser.php @@ -393,7 +393,7 @@ public function getObjectsForField($p) if (!empty($fieldConfig['source']['fn'])) { $method = explode('.', $fieldConfig['source']['fn']); $class = new $method[0](); - $rez = $class->$method[1]($p); + $rez = $class->{$method[1]}($p); // if custom source returned any result then return it right there // otherwise custom source can add some filtering params and we go further processing @@ -610,9 +610,12 @@ public function getObjectsForField($p) $search = new Search(); - // temporary: Don't use permissions for Objects fields - // it can be later reinforced per field in config - $p['skipSecurity'] = true; + + if (!isset($p['skipSecurity'])) { + // temporary: Don't use permissions for Objects fields + // it can be later reinforced per field in config + $p['skipSecurity'] = true; + } $rez = $search->query($p); $this->setCustomIcons($rez['data']); diff --git a/httpsdocs/classes/CB/DataModel/Tree.php b/httpsdocs/classes/CB/DataModel/Tree.php index 9077a26c..7e0ea9e2 100644 --- a/httpsdocs/classes/CB/DataModel/Tree.php +++ b/httpsdocs/classes/CB/DataModel/Tree.php @@ -214,7 +214,7 @@ public static function getCaseId($id) } /** - * Update owner for ginev ids + * Update owner for given ids * @param int $ids * @param int $ownerId * @return void @@ -239,6 +239,33 @@ public static function updateOwner($ids, $ownerId) } } + /** + * Get owner for given id + * @param int $id + * @return int + */ + public static function getOwner($id) + { + $rez = null; + + $res = DB\dbQuery( + 'SELECT oid + FROM tree + WHERE id = $1', + array( + $id + ) + ); + + if ($r = $res->fetch_assoc()) + $rez = $r['oid']; + + $res->close(); + // var_dump($rez); + + return $rez; + } + public static function getRootId() { $rez = null; diff --git a/httpsdocs/classes/CB/Objects/Object.php b/httpsdocs/classes/CB/Objects/Object.php index 83d62ae1..e12eed60 100644 --- a/httpsdocs/classes/CB/Objects/Object.php +++ b/httpsdocs/classes/CB/Objects/Object.php @@ -195,7 +195,12 @@ protected function collectModelData() $p['cid'] = User::getId(); } if (empty($p['oid'])) { - $p['oid'] = $p['cid']; + $oid = DM\Tree::getOwner($p['id']); + + if (empty($oid)) + $p['oid'] = $p['cid']; + else + $p['oid'] = $oid; } if (empty($p['cdate'])) { diff --git a/httpsdocs/js/app.js b/httpsdocs/js/app.js index 9426fe9e..a0ad76ad 100644 --- a/httpsdocs/js/app.js +++ b/httpsdocs/js/app.js @@ -981,6 +981,26 @@ function initApp() { } break; + case 'int': + ed = new Ext.form.Int({ + data: objData + + ,plugins: [{ + ptype: 'CBPluginFieldDropDownList' + ,commands: [ + { + prefix: ' ' + ,regex: /^([\w\d_\.]+)/i + + ,insertField: 'info' + + ,handler: CB.plugin.field.DropDownList.prototype.onAtCommand + } + ] + }] + }); + break; + case 'text': ed = new Ext.form.Text({ data: objData diff --git a/httpsdocs/js/widget/block/Chart.js b/httpsdocs/js/widget/block/Chart.js index f9df1690..62071beb 100644 --- a/httpsdocs/js/widget/block/Chart.js +++ b/httpsdocs/js/widget/block/Chart.js @@ -22,11 +22,22 @@ Ext.define('CB.widget.block.Chart', { type: 'vbox' ,pack: 'top' } - ,listeners: { scope: this ,afterrender: this.onAfterRender } + ,dockedItems: [{ + xtype: 'toolbar', + items: [{ + xtype: 'button', + text: 'Download Chart as PNG Image', + handler: function(btn, e, eOpts) { + btn.up('CBWidgetBlockChart').down("chart").save({ + type: "image/png" + }) + } + }] + }] }); this.callParent(arguments); @@ -55,17 +66,21 @@ Ext.define('CB.widget.block.Chart', { trackMouse: true ,style: 'background: #FFF; overflow: visible' ,height: 20 - ,width: 200 + ,width: 'auto' ,renderer: function(storeItem, item) { this.setTitle(storeItem.get('name') + ': ' + storeItem.get('count')); } - }; + };console.log('colors', App.colors); this.chartConfigs = { 'bar': { width: '100%' ,store: this.chartDataStore ,colors: App.colors + ,resizable: { + pinned: true, + handles: 'all' + } ,axes: [ { type: 'numeric' @@ -102,12 +117,22 @@ Ext.define('CB.widget.block.Chart', { scope: this ,itemclick: this.onChartItemClick } + ,renderer: function (sprite, record, attr, index, store) { + var colorChoice = index % App.colors.length; + return Ext.apply(attr, { + fill: App.colors[colorChoice] + }); + } }] } ,'column': { width: '100%' ,store: this.chartDataStore ,colors: App.colors + ,resizable: { + pinned: true, + handles: 'all' + } ,axes: [{ type: 'numeric' ,position: 'left' @@ -141,11 +166,22 @@ Ext.define('CB.widget.block.Chart', { scope: this ,itemclick: this.onChartItemClick } + ,renderer: function (sprite, record, attr, index, store) { + var colorChoice = index % App.colors.length; + return Ext.apply(attr, { + fill: App.colors[colorChoice] + }); + } }] } ,'pie': { width: '100%' + ,resizable: { + pinned: true, + handles: 'all' + } ,store: this.chartDataStore + ,interactions: ['rotate'] ,series: [{ type: 'pie', donut: 0, @@ -153,7 +189,22 @@ Ext.define('CB.widget.block.Chart', { label: { field: 'shortname', display: 'outside', - calloutLine: true + calloutLine: true, + renderer: function (value, sprite, config, renderData, index) { + /* + * update in task_#392 + * hide labels where sections are too small to avoid labels + * overlapping and making the chart unreable + */ + var angle = Math.abs(renderData.endAngle - renderData.startAngle); + /* + * the threshold selected here is arbitrary and seemed to work + * well for the charts I tested with. + */ + var threshold = 400; + if (angle > threshold) return value; + return ''; + } }, showInLegend: true ,highlight: true @@ -252,6 +303,7 @@ Ext.define('CB.widget.block.Chart', { this.removeAll(true); var cfg = Ext.clone(this.chartConfigs[charts[0]]); + var chartClass = 'Ext.chart.Chart'; if(!Ext.isEmpty(cfg)) { // cfg.height = Math.max(cfg.store.getCount() * 25, 300); diff --git a/httpsdocs/lib/install_functions.php b/httpsdocs/lib/install_functions.php index e5db7ff1..b0e4607c 100644 --- a/httpsdocs/lib/install_functions.php +++ b/httpsdocs/lib/install_functions.php @@ -407,7 +407,7 @@ function verifyDBConfig(&$cfg) $success = true; try { - @new \mysqli( + new \mysqli( $cfg['db_host'], $cfg['su_db_user'], (isset($cfg['su_db_pass']) ? $cfg['su_db_pass'] : null), diff --git a/install/mysql/_casebox.sql b/install/mysql/_casebox.sql index e91c25f1..cc0806e9 100644 --- a/install/mysql/_casebox.sql +++ b/install/mysql/_casebox.sql @@ -60870,7 +60870,7 @@ insert into `translations`(`id`,`pid`,`name`,`en`,`es`,`ge`,`fr`,`hy`,`pt`,`ro` (3089,NULL,'ErrorCreatingPreview','Preview not available at this moment, please download the file','Preview not available at this moment, please download the file','Preview not available at this moment, please download the file','Preview not available at this moment, please download the file','Preview not available at this moment, please download the file','Preview not available at this moment, please download the file','Preview not available at this moment, please download the file','Preview not available at this moment, please download the file','Preview not available at this moment, please download the file','Preview not available at this moment, please download the file',1,NULL,0), -(3090,NULL,'MailInviteSubject','[Casebox] {creatorFullName} ({creatorEmail}) ivited you to {projectTitle}','[Casebox] {creatorFullName} ({creatorEmail}) ivited you to {projectTitle}','[Casebox] {creatorFullName} ({creatorEmail}) ivited you to {projectTitle}','[Casebox] {creatorFullName} ({creatorEmail}) ivited you to {projectTitle}','[Casebox] {creatorFullName} ({creatorEmail}) ivited you to {projectTitle}','[Casebox] {creatorFullName} ({creatorEmail}) ivited you to {projectTitle}','[Casebox] {creatorFullName} ({creatorEmail}) ivited you to {projectTitle}','[Casebox] {creatorFullName} ({creatorEmail}) ivited you to {projectTitle}','[Casebox] {creatorFullName} ({creatorEmail}) ivited you to {projectTitle}','[Casebox] {creatorFullName} ({creatorEmail}) ivited you to {projectTitle}',1,NULL,0), +(3090,NULL,'MailInviteSubject','[Casebox] {creatorFullName} ({creatorEmail}) invited you {username} ({userEmail}) to {projectTitle}','[Casebox] {creatorFullName} ({creatorEmail}) invited you {username} ({userEmail}) to {projectTitle}','[Casebox] {creatorFullName} ({creatorEmail}) invited you {username} ({userEmail}) to {projectTitle}','[Casebox] {creatorFullName} ({creatorEmail}) invited you {username} ({userEmail}) to {projectTitle}','[Casebox] {creatorFullName} ({creatorEmail}) invited you {username} ({userEmail}) to {projectTitle}','[Casebox] {creatorFullName} ({creatorEmail}) invited you {username} ({userEmail}) to {projectTitle}','[Casebox] {creatorFullName} ({creatorEmail}) invited you {username} ({userEmail}) to {projectTitle}','[Casebox] {creatorFullName} ({creatorEmail}) invited you {username} ({userEmail}) to {projectTitle}','[Casebox] {creatorFullName} ({creatorEmail}) invited you {username} ({userEmail}) to {projectTitle}','[Casebox] {creatorFullName} ({creatorEmail}) invited you {username} ({userEmail}) to {projectTitle}',1,NULL,0), (3091,NULL,'FollowText','Follow get notified in app & email','Follow get notified in app & email','Follow get notified in app & email','Follow get notified in app & email','Follow get notified in app & email','Follow get notified in app & email','Follow get notified in app & email','Follow get notified in app & email','Follow get notified in app & email','Follow get notified in app & email',2,NULL,0), diff --git a/logs/.gitignore b/logs/.gitignore old mode 100644 new mode 100755 diff --git a/sys/templates/email_invite.html b/sys/templates/email_invite.html index bb323d7d..9a30f2e8 100644 --- a/sys/templates/email_invite.html +++ b/sys/templates/email_invite.html @@ -103,7 +103,7 @@

- {creatorFullName} ({creatorEmail}) has invited you to {projectTitle}. + {creatorFullName} ({creatorEmail}) has invited you {username} ({userEmail}) to {projectTitle}.