diff --git a/.gitignore b/.gitignore index 791b4ee4..73fef35e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ bin/setup.conf .DS_Store update.sh .* +site/ /nbproject diff --git a/CRM/Volunteer/APIWrapper/CustomField.php b/CRM/Volunteer/APIWrapper/CustomField.php new file mode 100644 index 00000000..2b024691 --- /dev/null +++ b/CRM/Volunteer/APIWrapper/CustomField.php @@ -0,0 +1,63 @@ + $customFieldValue) { + $key = 'custom_' . $customFieldId; + // If custom field have set value. + if($customFieldValue) { + // Explode with seperator. + $seperator = CRM_CORE_DAO::VALUE_SEPARATOR; + $newCustomFieldValue = explode($seperator, $customFieldValue); + // If multiple value then prepare associative array for that custom field. + // Set value under key. + if(count($newCustomFieldValue) > 1) { + $customFieldValueArray = array(); + foreach ($newCustomFieldValue as $new_key => $value) { + if($value) { + $customFieldValueArray[$value] = $value; + } + } + $customFieldValue = $customFieldValueArray; + } + } + $item[$key] = $customFieldValue; + } + } + } + + return $result; + } + +} diff --git a/CRM/Volunteer/Angular.php b/CRM/Volunteer/Angular.php index d4910393..463f98be 100644 --- a/CRM/Volunteer/Angular.php +++ b/CRM/Volunteer/Angular.php @@ -25,7 +25,22 @@ public static function load($defaultRoute) { CRM_Core_Resources::singleton()->addScriptFile('civicrm', 'packages/jquery/plugins/jquery.notify.min.js', 10, 'html-header'); $loader = new \Civi\Angular\AngularLoader(); - $loader->setModules(array('volunteer')); + + + // Check if fieldmeta extension is installed or not. + // If installed then add crmFieldMetadata module. + $result = civicrm_api3('Extension', 'get', [ + 'sequential' => 1, + 'full_name' => "org.civicrm.fieldmetadata", + 'status' => "installed", + ]); + + if($result['count']) { + $loader->setModules(array('volunteer', 'crmFieldMetadata')); + } else { + $loader->setModules(array('volunteer')); + } + $loader->setPageName('civicrm/vol'); $loader->load(); \Civi::resources()->addSetting(array( diff --git a/CRM/Volunteer/Angular/Manager.php b/CRM/Volunteer/Angular/Manager.php new file mode 100644 index 00000000..384bb41f --- /dev/null +++ b/CRM/Volunteer/Angular/Manager.php @@ -0,0 +1,107 @@ +modules === NULL) { + $config = \CRM_Core_Config::singleton(); + + $angularModules = array(); + //$angularModules['angularFileUpload'] = array( + // 'ext' => 'civicrm', + // 'js' => array('bower_components/angular-file-upload/angular-file-upload.min.js'), + //); + $angularModules['crmApp'] = array( + 'ext' => 'civicrm', + 'js' => array('ang/crmApp.js'), + ); + //$angularModules['crmAttachment'] = array( + // 'ext' => 'civicrm', + // 'js' => array('ang/crmAttachment.js'), + // 'css' => array('ang/crmAttachment.css'), + // 'partials' => array('ang/crmAttachment'), + // 'settings' => array( + // 'token' => \CRM_Core_Page_AJAX_Attachment::createToken(), + // ), + //); + $angularModules['crmAutosave'] = array( + 'ext' => 'civicrm', + 'js' => array('ang/crmAutosave.js'), + ); + $angularModules['crmCxn'] = array( + 'ext' => 'civicrm', + 'js' => array('ang/crmCxn.js', 'ang/crmCxn/*.js'), + 'css' => array('ang/crmCxn.css'), + 'partials' => array('ang/crmCxn'), + ); + $angularModules['crmResource'] = array( + 'ext' => 'civicrm', + // 'js' => array('js/angular-crmResource/byModule.js'), // One HTTP request per module. + 'js' => array('js/angular-crmResource/all.js'), // One HTTP request for all modules. + ); + $angularModules['crmUi'] = array( + 'ext' => 'civicrm', + 'js' => array('ang/crmUi.js'), + 'partials' => array('ang/crmUi'), + 'settings' => array( + 'browseUrl' => $config->userFrameworkResourceURL . 'packages/kcfinder/browse.php', + 'uploadUrl' => $config->userFrameworkResourceURL . 'packages/kcfinder/upload.php', + ), + ); + $angularModules['crmUtil'] = array( + 'ext' => 'civicrm', + 'js' => array('ang/crmUtil.js'), + ); + // https://github.com/jwstadler/angular-jquery-dialog-service + $angularModules['dialogService'] = array( + 'ext' => 'civicrm', + 'js' => array('bower_components/angular-jquery-dialog-service/dialog-service.js'), + ); + $angularModules['ngRoute'] = array( + 'ext' => 'civicrm', + 'js' => array('bower_components/angular-route/angular-route.min.js'), + ); + $angularModules['ngSanitize'] = array( + 'ext' => 'civicrm', + 'js' => array('bower_components/angular-sanitize/angular-sanitize.min.js'), + ); + $angularModules['ui.utils'] = array( + 'ext' => 'civicrm', + 'js' => array('bower_components/angular-ui-utils/ui-utils.min.js'), + ); + $angularModules['ui.sortable'] = array( + 'ext' => 'civicrm', + 'js' => array('bower_components/angular-ui-sortable/sortable.min.js'), + ); + $angularModules['unsavedChanges'] = array( + 'ext' => 'civicrm', + 'js' => array('bower_components/angular-unsavedChanges/dist/unsavedChanges.min.js'), + ); + + \CRM_Utils_Hook::angularModules($angularModules); + + // Filter out modules which should not be loaded on Volunteer's base page + foreach ($angularModules as $name => $module) { + // Angular modules can register to be loaded by supplying a truthy + // property 'volunteer' as a sibling to 'ext' and 'js' + $moduleSelfRegisters = !empty($module['volunteer']); + + // These extensions are always allowed. (Note: the modules associated + // with the civicrm "extension" are already filtered above.) + $whitelist = array('civicrm', 'org.civicrm.angularprofiles', 'org.civicrm.fieldmetadata',); + + if (!$moduleSelfRegisters && !in_array($module['ext'], $whitelist)) { + unset($angularModules[$name]); + } + } + + $this->modules = $this->resolvePatterns($angularModules); + } + + return $this->modules; + } + +} \ No newline at end of file diff --git a/CRM/Volunteer/BAO/NeedSearch.php b/CRM/Volunteer/BAO/NeedSearch.php index 33e28ac4..7d6684ee 100644 --- a/CRM/Volunteer/BAO/NeedSearch.php +++ b/CRM/Volunteer/BAO/NeedSearch.php @@ -66,35 +66,170 @@ private function getDefaultSearchParams() { * @return array $this->searchResults */ public function search() { - $projects = CRM_Volunteer_BAO_Project::retrieve($this->searchParams['project']); - foreach ($projects as $project) { - $results = array(); - - $flexibleNeed = civicrm_api3('VolunteerNeed', 'getsingle', array( - 'id' => $project->flexible_need_id, - )); - if ($flexibleNeed['visibility_id'] === CRM_Core_OptionGroup::getValue('visibility', 'public', 'name')) { - $needId = $flexibleNeed['id']; - $results[$needId] = $flexibleNeed; + // Get volunteer_role_option_group_id of volunteer_role". + $result = civicrm_api3('OptionGroup', 'get', [ + 'sequential' => 1, + 'name' => "volunteer_role", + ]); + $volunteer_role_option_group_id = $result['id']; + + // Prepare select query for preparing fetch opportunity. + // Join relevant table of need. + $select = " SELECT project.id,project.title, project.description, project.is_active, project.loc_block_id, project.campaign_id, need.id as need_id, need.start_time, need.end_time, need.is_flexible, need.visibility_id, need.is_active as need_active,need.created as need_created,need.last_updated as need_last_updated,need.role_id as role_id, addr.street_address, addr.city, addr.postal_code, country.name as country, state.name as state_province, opt.label as role_lable, opt.description as role_description, campaign.title as campaign_title "; + $from = " FROM civicrm_volunteer_project AS project"; + $join = " LEFT JOIN civicrm_volunteer_need AS need ON (need.project_id = project.id) "; + $join .= " LEFT JOIN civicrm_loc_block AS loc ON (loc.id = project.loc_block_id) "; + $join .= " LEFT JOIN civicrm_address AS addr ON (addr.id = loc.address_id) "; + $join .= " LEFT JOIN civicrm_country AS country ON (country.id = addr.country_id) "; + $join .= " LEFT JOIN civicrm_state_province AS state ON (state.id = addr.state_province_id) "; + $join .= " LEFT JOIN civicrm_campaign AS campaign ON (campaign.id = project.campaign_id) "; + // Get beneficiary_rel_no for volunteer_project_relationship type. + $beneficiary_rel_no = CRM_Core_PseudoConstant::getKey("CRM_Volunteer_BAO_ProjectContact", 'relationship_type_id', 'volunteer_beneficiary'); + + // Join Project Contact table for benificiary for specific $beneficiary_rel_no. + $join .= " LEFT JOIN civicrm_volunteer_project_contact AS pc ON (pc.project_id = project.id And pc.relationship_type_id='".$beneficiary_rel_no."') "; + // Join civicrm_option_value table for role details of need. + $join .= " LEFT JOIN civicrm_option_value AS opt ON (opt.value = need.role_id And opt.option_group_id='".$volunteer_role_option_group_id."') "; + // Join civicrm_contact table for contact details. + $join .= " LEFT JOIN civicrm_contact AS cc ON (cc.id = pc.contact_id) "; + $select .= ", GROUP_CONCAT( cc.id ) as beneficiary_id , GROUP_CONCAT( cc.display_name ) as beneficiary_display_name"; + + $visibility_id = CRM_Volunteer_BAO_Project::getVisibilityId('name', "public"); + $where = " Where project.is_active = 1 AND need.visibility_id = ".$visibility_id; + // Default Filter parameter of date start and date end field of need table. + if(empty($this->searchParams['need']['date_start']) && empty($this->searchParams['need']['date_end'])) { + $where .= " AND ( + (DATE_FORMAT(need.start_time,'%Y-%m-%d') <= CURDATE() AND DATE_FORMAT(need.end_time,'%Y-%m-%d') >= CURDATE()) OR + (DATE_FORMAT(need.start_time,'%Y-%m-%d') >= CURDATE()) OR (DATE_FORMAT(need.end_time,'%Y-%m-%d') >= CURDATE()) OR + (need.start_time Is NOT NULL && need.end_time IS NULL) OR + (need.start_time Is NULL && need.end_time IS NULL) + )"; + } + // Add date start and date end filter if passed in UI. + if($this->searchParams['need']['date_start'] && $this->searchParams['need']['date_end']) { + $start_time = date("Y-m-d", $this->searchParams['need']['date_start']); + $end_time = date("Y-m-d", $this->searchParams['need']['date_end']); + $where .= " AND ( + ( need.end_time IS NOT NULL AND need.start_time IS NOT NULL + AND ( + DATE_FORMAT(need.start_time,'%Y-%m-%d')<='".$end_time."' AND DATE_FORMAT(need.start_time,'%Y-%m-%d')>='".$start_time."' + OR + DATE_FORMAT(need.end_time,'%Y-%m-%d') >= '".$start_time."' AND DATE_FORMAT(need.end_time,'%Y-%m-%d') <= '".$end_time."' + ) + ) OR ( + need.end_time IS NULL AND DATE_FORMAT(need.start_time,'%Y-%m-%d')>='".$start_time."' AND DATE_FORMAT(need.start_time,'%Y-%m-%d')<='".$end_time."' + ) + )"; + } else { // one but not the other supplied: + if($this->searchParams['need']['date_start']) { + $start_time = date("Y-m-d", $this->searchParams['need']['date_start']); + $where .= " And (DATE_FORMAT(need.start_time,'%Y-%m-%d')>='".$start_time."')"; } - - $openNeeds = $project->open_needs; - foreach ($openNeeds as $key => $need) { - if ($this->needFitsSearchCriteria($need)) { - $results[$key] = $need; + if($this->searchParams['need']['date_end']) { + $end_time = date("Y-m-d", $this->searchParams['need']['date_end']); + $where .= " And (DATE_FORMAT(need.end_time,'%Y-%m-%d')<='".$end_time."')"; + } + } + // Add role filter if passed in UI. + if($this->searchParams['need']['role_id'] && is_array($this->searchParams['need']['role_id'])) { + $role_id_string = implode(",", $this->searchParams['need']['role_id']); + $where .= " And need.role_id IN (".$role_id_string.")"; + } + // Add with(benificiary) filter if passed in UI. + if($this->searchParams['project']['project_contacts']['volunteer_beneficiary']) { + $beneficiary_id_string = implode(",", $this->searchParams['project']['project_contacts']['volunteer_beneficiary']); + $where .= " And pc.contact_id IN (".$beneficiary_id_string.")"; + } + // Add Location filter if passed in UI. + if(isset($this->searchParams['project']["proximity"]) && !empty($this->searchParams['project']["proximity"])) { + $proximityquery = CRM_Volunteer_BAO_Project::buildProximityWhere($this->searchParams['project']["proximity"]); + $proximityquery = str_replace("civicrm_address", "addr", $proximityquery); + $where .= " And ".$proximityquery; + } + // If Project Id is passed from URL- Query String. + if(isset($this->searchParams['project']) && !empty($this->searchParams['project'])) { + if(isset($this->searchParams['project']['is_active']) && isset($this->searchParams['project']['id'])) { + $where .= " And project.id=".$this->searchParams['project']['id']; + } + } + // Order by Logic. + $orderByColumn = "project.id"; + $order = "ASC"; + $orderby = " group by need.id ORDER BY " . $orderByColumn . " " . $order; + + // Pagination Logic. + $no_of_records_per_page = 10; + if(isset($params['page_no']) && !empty($params['page_no'])) { + $page_no = $params['page_no']; + } else { + $page_no = 1; + } + $offset = ($page_no-1) * $no_of_records_per_page; + $limit = " LIMIT ".$offset.", ".$no_of_records_per_page; + // Prepare whole sql query dynamic. + //$sql = $select . $from . $join . $where . $orderby . $limit; + $sql = $select . $from . $join . $where . $orderby; + $dao = new CRM_Core_DAO(); + $dao->query($sql); + $project_opportunities = []; + $i=0; + $config = CRM_Core_Config::singleton(); + $timeFormat = $config->dateformatDatetime; + // Prepare array for need of projects. + while ($dao->fetch()) { + $project_opportunities[$i]['id'] = $dao->need_id; + $project_opportunities[$i]['project_id'] = $dao->id; + $project_opportunities[$i]['is_flexible'] = $dao->is_flexible; + $project_opportunities[$i]['visibility_id'] = $dao->visibility_id; + $project_opportunities[$i]['is_active'] = $dao->need_active; + $project_opportunities[$i]['created'] = $dao->need_created; + $project_opportunities[$i]['last_updated'] = $dao->need_last_updated; + if(isset($dao->start_time) && !empty($dao->start_time)) { + $start_time = CRM_Utils_Date::customFormat($dao->start_time, $timeFormat); + if(isset($dao->end_time) && !empty($dao->end_time)) { + $end_time = CRM_Utils_Date::customFormat($dao->end_time, $timeFormat); + $project_opportunities[$i]['display_time'] = $start_time ." - ". $end_time; + } else { + $project_opportunities[$i]['display_time'] = $start_time; } + } else { + $project_opportunities[$i]['display_time'] = "Any"; } - - if (!empty($results)) { - $this->projects[$project->id] = array(); + $project_opportunities[$i]['role_id'] = $dao->role_id; + if(empty($dao->role_lable)) { + $project_opportunities[$i]['role_label'] = "Any"; + } else { + $project_opportunities[$i]['role_label'] = $dao->role_lable; } - - $this->searchResults += $results; + $project_opportunities[$i]['role_description'] = $dao->role_description; + $project_opportunities[$i]['project']['description'] = $dao->description; + $project_opportunities[$i]['project']['id'] = $dao->id; + $project_opportunities[$i]['project']['title'] = $dao->title; + $project_opportunities[$i]['project']['campaign_title'] = $dao->campaign_title; + $project_opportunities[$i]['project']['location'] = array( + "city" => $dao->city, + "country" => $dao->country, + "postal_code" => $dao->postal_code, + "state_province" => $dao->state_province, + "street_address" => $dao->street_address + ); + $beneficiary_display_name = explode(',', $dao->beneficiary_display_name); + if(isset($beneficiary_display_name) && !empty($beneficiary_display_name) && is_array($beneficiary_display_name)) { + $beneficiary_id_array = explode(',', $dao->beneficiary_id); + foreach ($beneficiary_display_name as $key => $display_name) { + $project_opportunities[$i]['project']['beneficiaries'][$key] = array( + "id" => $beneficiary_id_array[$key], + "display_name" => $display_name + ); + } + } else { + $project_opportunities[$i]['project']['beneficiaries'] = $dao->beneficiary_display_name; + $project_opportunities[$i]['project']['beneficiary_id'] = $dao->beneficiary_id; + } + $i++; } - $this->getSearchResultsProjectData(); - usort($this->searchResults, array($this, "usortDateAscending")); - return $this->searchResults; + return $project_opportunities; } /** @@ -305,4 +440,4 @@ private static function usortDateAscending($a, $b) { return ($startTimeA < $startTimeB) ? -1 : 1; } -} +} \ No newline at end of file diff --git a/CRM/Volunteer/BAO/Project.php b/CRM/Volunteer/BAO/Project.php index 000215de..5425ef46 100644 --- a/CRM/Volunteer/BAO/Project.php +++ b/CRM/Volunteer/BAO/Project.php @@ -463,9 +463,28 @@ public static function retrieve(array $params) { foreach ($project->fields() as $field) { $fieldName = $field['name']; - + // default sqlExpr: see CRM_Utils_SQL_BaseParamQuery::interpolate() + $comparator = "="; + $sqlExpr = '!column '.$comparator.' @value'; + // Check any field parameter set with array or not. + // If array then use comparator from key. Otheriwse use "=" comparator. if (!empty($project->$fieldName)) { - $query->where('!column = @value', array( + if(isset($project->$fieldName) && !empty($project->$fieldName) && is_array($project->$fieldName)) { + // Key contains comparator value. eg. "Like, Not Like etc" + $comparator = key($project->$fieldName); + if ($comparator == "IN") { + if (is_array($project->$fieldName)) { + $project->$fieldName = '('. implode(",",current($project->$fieldName )) .')'; + } + // disable escaping: "!value" + $sqlExpr = '!column '.$comparator.' !value'; + } else { + $sqlExpr = '!column '.$comparator.' @value'; + } + } + + // Use dynamic comparator based on passed parameter. + $query->where($sqlExpr, array( 'column' => $fieldName, 'value' => $project->$fieldName, )); @@ -539,7 +558,7 @@ private static function buildContactJoin(array $projectContacts) { * SQL fragment (partial where clause) * @throws Exception */ - private static function buildProximityWhere(array $params) { + public static function buildProximityWhere(array $params) { $country = $lat = $lon = $radius = $unit = NULL; extract($params, EXTR_IF_EXISTS); @@ -547,44 +566,44 @@ private static function buildProximityWhere(array $params) { if (!CRM_Utils_Rule::numeric($radius)) { throw new Exception(ts('Radius should exist and be numeric')); } + if(!isset($lat) && !isset($lon)) { + if (!CRM_Utils_Rule::numeric($lat) || !CRM_Utils_Rule::numeric($lon)) { + // try to supply a default country if none provided + if (empty($country)) { + $settings = civicrm_api3('Setting', 'get', array( + "return" => array("defaultContactCountry"), + "sequential" => 1, + )); + $country = $settings['values'][0]['defaultContactCountry']; + } + + if (empty($country)) { + throw new Exception(ts('Either Country or both Latitude and Longitude are required')); + } - if (!CRM_Utils_Rule::numeric($lat) || !CRM_Utils_Rule::numeric($lon)) { - // try to supply a default country if none provided - if (empty($country)) { - $settings = civicrm_api3('Setting', 'get', array( - "return" => array("defaultContactCountry"), - "sequential" => 1, - )); - $country = $settings['values'][0]['defaultContactCountry']; - } - - if (empty($country)) { - throw new Exception(ts('Either Country or both Latitude and Longitude are required')); - } - - // TODO: I think CRM_Utils_Geocode_*::format should be responsible for this - // If/when CRM-17245 is closed, this if-block can be removed. - if (CRM_Utils_Type::validate($country, 'Positive', FALSE)) { - $params['country'] = civicrm_api3('Country', 'getvalue', array( - 'id' => $country, - 'return' => 'name', - )); - } + // TODO: I think CRM_Utils_Geocode_*::format should be responsible for this + // If/when CRM-17245 is closed, this if-block can be removed. + if (CRM_Utils_Type::validate($country, 'Positive', FALSE)) { + $params['country'] = civicrm_api3('Country', 'getvalue', array( + 'id' => $country, + 'return' => 'name', + )); + } - // TODO: support other geocoders - $geocodeSuccess = CRM_Utils_Geocode_Google::format($params); - if (!$geocodeSuccess) { - // this is intentionally a string; a query like "SELECT * FROM foo WHERE FALSE" - // will return an empty set, which is what we should do if the provided address - // can't be geocoded - return 'FALSE'; + // TODO: support other geocoders + $geocodeSuccess = CRM_Utils_Geocode_Google::format($params); + if (!$geocodeSuccess) { + // this is intentionally a string; a query like "SELECT * FROM foo WHERE FALSE" + // will return an empty set, which is what we should do if the provided address + // can't be geocoded + return 'FALSE'; + } + // $params is passed to the geocoder by reference; on success, these values + // will be available + $lat = $params['geo_code_1']; + $lon = $params['geo_code_2']; } - // $params is passed to the geocoder by reference; on success, these values - // will be available - $lat = $params['geo_code_1']; - $lon = $params['geo_code_2']; } - $conversionFactor = ($unit == "mile") ? 1609.344 : 1000; //radius in meters $radius = $radius * $conversionFactor; @@ -910,7 +929,7 @@ private function _get_needs() { $result = civicrm_api3('VolunteerNeed', 'get', array( 'is_active' => '1', 'project_id' => $this->id, - 'visibility_id' => CRM_Core_OptionGroup::getValue('visibility', 'public', 'name'), + 'visibility_id' => CRM_Volunteer_BAO_Project::getVisibilityId('name', "public"), 'options' => array( 'sort' => 'start_time', 'limit' => 0, @@ -1004,4 +1023,16 @@ private function _get_flexible_need_id() { return self::getFlexibleNeedID($this->id); } + /* + * Implement getVisibilityId() function. + * Based on column and name it return visibility id. + */ + public static function getVisibilityId($column, $name) { + $visibility = CRM_Core_PseudoConstant::visibility($column); + $visibility_id = array_search ($name, $visibility); + + return $visibility_id; + } + + } diff --git a/CRM/Volunteer/BAO/VolunteerAppeal.php b/CRM/Volunteer/BAO/VolunteerAppeal.php new file mode 100644 index 00000000..48790ca9 --- /dev/null +++ b/CRM/Volunteer/BAO/VolunteerAppeal.php @@ -0,0 +1,596 @@ +imageUploadDir.'appeal/'; + $upload_appeal_main_directory = $config->imageUploadDir.'appeal/main/'; + $upload_appeal_thumb_directory = $config->imageUploadDir.'appeal/thumb/'; + $upload_appeal_medium_directory = $config->imageUploadDir.'appeal/medium/'; + // If appeal folder not exist, create appeal folder on civicrm.files folder. + if (!file_exists($upload_appeal_directory)) { + mkdir($upload_appeal_directory, 0777, TRUE); + } + // If main image folder not exist, create main folder under appeal folder on civicrm.files folder. + if (!file_exists($upload_appeal_main_directory)) { + mkdir($upload_appeal_main_directory, 0777, TRUE); + } + // If thumb image folder not exist, create thumb folder under appeal folder on civicrm.files folder. + if (!file_exists($upload_appeal_thumb_directory)) { + mkdir($upload_appeal_thumb_directory, 0777, TRUE); + } + // If medium image folder not exist, create medium folder under appeal folder on civicrm.files folder. + if (!file_exists($upload_appeal_medium_directory)) { + mkdir($upload_appeal_medium_directory, 0777, TRUE); + } + // If new image is updated then resize that image and move that into folder. + if(isset($params['image_data'])) { + $image_parts = explode(";base64,", $params['image_data']); + $image_base64 = base64_decode($image_parts[1]); + $current_time = time(); + $file = $upload_appeal_main_directory . $current_time."_".$params['image']; + file_put_contents($file, $image_base64); + + // Resize Image with 150*150 and save into destination folder. + $source_path = $upload_appeal_main_directory . $current_time."_".$params['image']; + $destination_path = $upload_appeal_thumb_directory . $current_time."_".$params['image']; + $destination_path_for_detail_image = $upload_appeal_medium_directory . $current_time."_".$params['image']; + + if (class_exists('Imagick')) { // Imagick resizing if available + + $imgSmall = new Imagick($source_path); + $imgProps = $imgSmall->getImageGeometry(); + $width = $imgProps['width']; + $height = $imgProps['height']; + $imgSmallDim = 150; + if ($width > $height) { + $newHeight = $imgSmallDim; + $newWidth = ($imgSmallDim / $height) * $width; + } else { + $newWidth = $imgSmallDim ; + $newHeight = ($imgSmallDim / $width) * $height; + } + $imgSmall->resizeImage($newWidth, $newHeight, Imagick::FILTER_CATROM, 1, true); + $imgSmall->cropImage($imgSmallDim, $imgSmallDim, floor(abs($newWidth-$imgSmallDim) / 2), floor(abs($newHeight-$imgSmallDim) / 2)); + $imgSmall->writeImage($destination_path); + + $imgMedium = new Imagick($source_path); + $imgProps = $imgMedium->getImageGeometry(); + $width = $imgProps['width']; + $height = $imgProps['height']; + $imgMediumDim = 300; + if ($width > $height) { + $newHeight = $imgMediumDim; + $newWidth = ($imgMediumDim / $height) * $width; + } else { + $newWidth = $imgMediumDim ; + $newHeight = ($imgMediumDim / $width) * $height; + } + $imgMedium->resizeImage($newWidth, $newHeight, Imagick::FILTER_CATROM, 1, true); + $imgMedium->cropImage($imgMediumDim, $imgMediumDim, floor(abs($newWidth-$imgMediumDim) / 2), floor(abs($newHeight-$imgMediumDim) / 2)); + $imgMedium->writeImage($destination_path_for_detail_image); + + } else if (function_exists('image_load')) { // native Drupal resizing + $imgSmall = image_load($source_path); + image_resize($imgSmall, 150, 150); + image_save($imgSmall, $destination_path); + image_resize($imgSmall, 300, 300); + image_save($imgSmall, $destination_path_for_detail_image); + } else { // resizing not supported + CRM_Core_Error::debug_log_message('Image resizing not supported for Volunteer Appeal images', FALSE, 'org.civicrm.volunteer'); + } + } + // If image is not updated on edit page, save old image name in database. + if($params['image'] == $params['old_image']) { + $params['image'] = $params['old_image']; + } else { + $params['image'] = $current_time."_".$params['image']; + } + + $appeal = new CRM_Volunteer_BAO_VolunteerAppeal(); + $appeal->copyValues($params); + $appeal->save(); + + // Custom data saved in database for appeal if user has set any. + $customData = CRM_Core_BAO_CustomField::postProcess($params, $appeal->id, 'VolunteerAppeal'); + if (!empty($customData)) { + CRM_Core_BAO_CustomValueTable::store($customData, 'civicrm_volunteer_appeal', $appeal->id); + } + + return $appeal; + } + + /** + * Strips invalid params, throws exception in case of unusable params. + * + * @param array $params + * Params for self::create(). + * @return array + * Filtered params. + * + * @throws Exception + * Via delegate. + */ + private static function validateCreateParams(array $params) { + if (empty($params['id']) && empty($params['title'])) { + CRM_Core_Error::fatal(ts('Title field is required for Appeal creation.')); + } + if (empty($params['id']) && empty($params['appeal_description'])) { + CRM_Core_Error::fatal(ts('Appeal Description field is required for Appeal creation.')); + } + + return $params; + } + + /** + * Get a list of Project Appeal matching the params. + * + * This function is invoked from within the web form layer and also from the + * API layer. Special params include: + * + * + * NOTE: This method does not return data related to the special params + * outlined above; however, these parameters can be used to filter the list + * of Projects appeal that is returned. + * + * @param array $params + * @return array of CRM_Volunteer_BAO_VolunteerAppeal objects + */ + public static function retrieve(array $params) { + $result = array(); + + $query = CRM_Utils_SQL_Select::from('`civicrm_volunteer_appeal` vp')->select('*'); + $appeal = new CRM_Volunteer_BAO_VolunteerAppeal(); + + $appeal->copyValues($params); + + foreach ($appeal->fields() as $field) { + $fieldName = $field['name']; + + if (!empty($appeal->$fieldName)) { + if(isset($appeal->$fieldName) && !empty($appeal->$fieldName) && is_array($appeal->$fieldName)) { + // Key contains comparator value. eg. "Like, Not Like etc" + $comparator = key($appeal->$fieldName); + } else { + $comparator = "="; + } + // Use dynamic comparator based on passed parameter. + $query->where('!column '.$comparator.' @value', array( + 'column' => $fieldName, + 'value' => $appeal->$fieldName, + )); + } + } + + // Get the global configuration. + $config = CRM_Core_Config::singleton(); + $upload_appeal_main_directory = $config->imageUploadDir.'appeal/main/'; + $upload_appeal_medium_directory = $config->imageUploadDir.'appeal/medium/'; + $upload_appeal_thumb_directory = $config->imageUploadDir.'appeal/thumb/'; + $default_image_name = "appeal-default-logo-sq.png"; + + $dao = self::executeQuery($query->toSQL()); + while ($dao->fetch()) { + $fetchedAppeal = new CRM_Volunteer_BAO_VolunteerAppeal(); + $daoClone = clone $dao; + $fetchedAppeal->copyValues($daoClone); + if($fetchedAppeal->image == "null" || !$fetchedAppeal->image) { + // check if the default image exists before we set the image property to it + if (file_exists($upload_appeal_main_directory . $default_image_name) + && file_exists($upload_appeal_medium_directory . $default_image_name) + && file_exists($upload_appeal_thumb_directory . $default_image_name) + ) { + $fetchedAppeal->image = $default_image_name; + } else { + $fetchedAppeal->image = null; + } + } + $result[(int) $dao->id] = $fetchedAppeal; + } + + + $dao->free(); + + return $result; + } + + /** + * Wrapper method for retrieve + * + * @param mixed $id Int or int-like string representing Appeal ID + * @return CRM_Volunteer_BAO_VolunteerAppeal + */ + public static function retrieveByID($id) { + $id = (int) CRM_Utils_Type::validate($id, 'Integer'); + // Get Appeal with location and location address based on appeal ID. + $api = civicrm_api3('VolunteerAppeal', 'getsingle', array( + 'id' => $id, + 'api.LocBlock.getsingle' => array( + 'api.Address.getsingle' => array(), + ), + )); + if (empty($api['loc_block_id']) || empty($api['api.LocBlock.getsingle']['address_id'])) { + $api['location'] = ""; + } else { + $address = ""; + if ($api['api.LocBlock.getsingle']['api.Address.getsingle']['street_address']) { + $address .= " ".$api['api.LocBlock.getsingle']['api.Address.getsingle']['street_address']; + } + if ($api['api.LocBlock.getsingle']['api.Address.getsingle']['street_address'] && ($api['api.LocBlock.getsingle']['api.Address.getsingle']['city'] || $api['api.LocBlock.getsingle']['api.Address.getsingle']['postal_code'])) { + $address .= '
'; + } + if ($api['api.LocBlock.getsingle']['api.Address.getsingle']['city']) { + $address .= " ".$api['api.LocBlock.getsingle']['api.Address.getsingle']['city']; + } + if ($api['api.LocBlock.getsingle']['api.Address.getsingle']['city'] && $api['api.LocBlock.getsingle']['api.Address.getsingle']['postal_code']) { + $address .= ', '.$api['api.LocBlock.getsingle']['api.Address.getsingle']['postal_code']; + } else if ($api['api.LocBlock.getsingle']['api.Address.getsingle']['postal_code']) { + $address .= $api['api.LocBlock.getsingle']['api.Address.getsingle']['postal_code']; + } + $api['location'] = $address; + } + // Get Project Details. + $api2 = civicrm_api3('VolunteerProject', 'getsingle', array( + 'id' => $api['project_id'], + 'api.LocBlock.getsingle' => array( + 'api.Address.getsingle' => array(), + ), + 'api.VolunteerProjectContact.get' => array( + 'options' => array('limit' => 0), + 'relationship_type_id' => 'volunteer_beneficiary', + 'api.Contact.get' => array( + 'options' => array('limit' => 0), + ), + ), + )); + $flexibleNeed = civicrm_api('volunteer_need', 'getvalue', array( + 'is_active' => 1, + 'is_flexible' => 1, + 'project_id' => $api['project_id'], + 'return' => 'id', + 'version' => 3, + )); + if (CRM_Utils_Array::value('is_error', $flexibleNeed) == 1) { + $flexibleNeed = NULL; + } else { + $flexibleNeed = (int) $flexibleNeed; + } + $project = CRM_Volunteer_BAO_Project::retrieveByID($api['project_id']); + $openNeeds = $project->open_needs; + $project = $project->toArray(); + foreach ($openNeeds as $key => $need) { + $project['available_shifts'][] = $need['display_time']; + } + if (empty($api2['loc_block_id']) || empty($api2['api.LocBlock.getsingle']['address_id'])) { + $api2['location'] = ""; + } else { + $address = ""; + if ($api2['api.LocBlock.getsingle']['api.Address.getsingle']['street_address']) { + $address .= " ".$api2['api.LocBlock.getsingle']['api.Address.getsingle']['street_address']; + } + if ($api2['api.LocBlock.getsingle']['api.Address.getsingle']['street_address'] && ($api2['api.LocBlock.getsingle']['api.Address.getsingle']['city'] || $api2['api.LocBlock.getsingle']['api.Address.getsingle']['postal_code'])) { + $address .= '
'; + } + if ($api2['api.LocBlock.getsingle']['api.Address.getsingle']['city']) { + $address .= " ".$api2['api.LocBlock.getsingle']['api.Address.getsingle']['city']; + } + if ($api2['api.LocBlock.getsingle']['api.Address.getsingle']['city'] && $api2['api.LocBlock.getsingle']['api.Address.getsingle']['postal_code']) { + $address .= ', '.$api2['api.LocBlock.getsingle']['api.Address.getsingle']['postal_code']; + } else if ($api2['api.LocBlock.getsingle']['api.Address.getsingle']['postal_code']) { + $address .= $api2['api.LocBlock.getsingle']['api.Address.getsingle']['postal_code']; + } + $project['project_location'] = $address; + } + foreach ($api2['api.VolunteerProjectContact.get']['values'] as $projectContact) { + if (!array_key_exists('beneficiaries', $project)) { + $project['beneficiaries'] = array(); + } + $project['beneficiaries'][] = array( + 'id' => $projectContact['contact_id'], + 'display_name' => $projectContact['api.Contact.get']['values'][0]['display_name'], + 'image_URL' => $projectContact['api.Contact.get']['values'][0]['image_URL'], + 'email' => $projectContact['api.Contact.get']['values'][0]['email'], + ); + } + $api['project'] = $project; + $api['project']['flexibleNeed'] = $flexibleNeed; + + return $api; + } + + + /** + * @inheritDoc This override adds a little data massaging prior to calling its + * parent. + * + * @deprecated since version 4.7.21-2.3.0 + * Internal core methods should not be extended by third-party code. + */ + public function copyValues(&$params, $serializeArrays = FALSE) { + if (is_a($params, 'CRM_Core_DAO')) { + $params = get_object_vars($params); + } + + if (array_key_exists('is_active', $params)) { + /* + * don't force is_active to have a value if none was set, to allow searches + * where the is_active state of appeal is irrelevant + */ + $params['is_active'] = CRM_Volunteer_BAO_VolunteerAppeal::isOff($params['is_active']) ? 0 : 1; + } + return parent::copyValues($params, $serializeArrays); + } + + /** + * Invoked from the API layer. + * + * Fetch appeal based on search parameter. + * @param array $params + * @return array $appeals + */ + public static function doSearch($params) { + + $show_beneficiary_at_front = 1; + $seperator = CRM_CORE_DAO::VALUE_SEPARATOR; + + $select = " SELECT (select id from civicrm_volunteer_need where civicrm_volunteer_need.is_flexible = 1 and civicrm_volunteer_need.project_id=p.id) as need_flexi_id, appeal.*, addr.street_address, addr.city, addr.postal_code"; + $select .= " , GROUP_CONCAT(DISTINCT need.id ) as need_id";//,mdt.need_start_time + $from = " FROM civicrm_volunteer_appeal AS appeal"; + $join = " LEFT JOIN civicrm_volunteer_project AS p ON (p.id = appeal.project_id) "; + $join .= " LEFT JOIN civicrm_loc_block AS loc ON (loc.id = appeal.loc_block_id) "; + $join .= " LEFT JOIN civicrm_address AS addr ON (addr.id = loc.address_id) "; + $join .= " LEFT JOIN civicrm_volunteer_need AS need ON (need.project_id = p.id) And need.is_active = 1 And need.is_flexible = 1 And need.visibility_id = 1"; + if($show_beneficiary_at_front == 1) { + $beneficiary_rel_no = CRM_Core_PseudoConstant::getKey("CRM_Volunteer_BAO_ProjectContact", 'relationship_type_id', 'volunteer_beneficiary'); + $join .= " LEFT JOIN civicrm_volunteer_project_contact AS pc ON (pc.project_id = p.id And pc.relationship_type_id='".$beneficiary_rel_no."') "; + $join .= " LEFT JOIN civicrm_contact AS cc ON (cc.id = pc.contact_id) "; + $select .= " , GROUP_CONCAT(DISTINCT cc.display_name ) as beneficiary_display_name, GROUP_CONCAT(DISTINCT cc.id ) as beneficiary_id"; + } + // Appeal should be active, Current Date between appeal date and related project should be active. + $where = " Where p.is_active = 1 And appeal.is_appeal_active = 1 And CURDATE() between appeal.active_fromdate and appeal.active_todate "; + + if(isset($params['search_appeal'])) { + $search_appeal = $params['search_appeal']; + $search_appeal = trim($search_appeal); + $where .= " And (appeal.title Like '%".$search_appeal."%' OR appeal.appeal_description Like '%".$search_appeal."%' OR cc.display_name LIKE '%".$search_appeal."%')"; + } + $having = ""; + // Handle beneficiary filter. + if($params['beneficiary']) { + $params['beneficiary'] = rtrim($params['beneficiary'], ','); + $beneficiary = explode(',',$params['beneficiary']); + if (sizeof($beneficiary) > 1) { + $lastElement = end($beneficiary); + $having = " HAVING ("; + foreach ($beneficiary as $key => $benificiery_id) { + if($benificiery_id) { + if($benificiery_id == $lastElement) { + $having .= " FIND_IN_SET(".$benificiery_id.", beneficiary_id)"; + } else { + $having .= " FIND_IN_SET(".$benificiery_id.", beneficiary_id) OR"; + } + } + } + $having .= ")"; + } else { + $having = " HAVING FIND_IN_SET(".$beneficiary[0].", beneficiary_id)"; + } + } + //Advance search parameter. + if(isset($params["advanced_search"])) { + // If start date and end date filter passed on advance search. + if($params["advanced_search"]["fromdate"] && $params["advanced_search"]["todate"]) { + $select .= " , GROUP_CONCAT(DISTINCT advance_need.id ) as need_shift_id"; + $join .= " LEFT JOIN civicrm_volunteer_need as advance_need ON (advance_need.project_id = p.id) And advance_need.is_active = 1 And advance_need.visibility_id = 1 and advance_need.is_flexible=0"; + + $start_time = $params["advanced_search"]["fromdate"]; + $end_time = $params["advanced_search"]["todate"]; + $where .= " AND ( + ( + (advance_need.end_time IS NULL AND DATE_FORMAT(advance_need.start_time,'%Y-%m-%d')>='".$start_time."') OR (DATE_FORMAT(advance_need.start_time,'%Y-%m-%d')>='".$start_time."' and DATE_FORMAT(advance_need.end_time,'%Y-%m-%d')<='".$end_time."') + ) + )"; + } else { // one but not the other supplied: + $select .= " , GROUP_CONCAT(DISTINCT advance_need.id ) as need_shift_id"; + $join .= " LEFT JOIN civicrm_volunteer_need as advance_need ON (advance_need.project_id = p.id) And advance_need.is_active = 1 And advance_need.visibility_id = 1 and advance_need.is_flexible=0"; + if($params["advanced_search"]["fromdate"]) { + $where .= " And (DATE_FORMAT(advance_need.start_time,'%Y-%m-%d')>='".$params["advanced_search"]["fromdate"]."')"; + } + if($params["advanced_search"]["todate"]) { + $where .= " And (DATE_FORMAT(advance_need.end_time,'%Y-%m-%d')<='".$params["advanced_search"]["todate"]."')"; + } + } + + // If show appeals done anywhere passed on advance search. + if(isset($params["advanced_search"]["show_appeals_done_anywhere"]) && $params["advanced_search"]["show_appeals_done_anywhere"] == true ) { + $where .= " And appeal.location_done_anywhere = 1 "; + } else { + // If show appeal is not set then check postal code, radius and proximity. + if(isset($params["advanced_search"]["proximity"]['postal_code']) || (isset($params["advanced_search"]["proximity"]['lat']) && isset($params["advanced_search"]["proximity"]['lon']))) { + $proximityquery = CRM_Volunteer_BAO_Project::buildProximityWhere($params["advanced_search"]["proximity"]); + $proximityquery = str_replace("civicrm_address", "addr", $proximityquery); + $where .= " And ".$proximityquery; + } + } + // If custom field pass from advance search filter. + if(isset($params["advanced_search"]["appealCustomFieldData"]) && !empty($params["advanced_search"]["appealCustomFieldData"])) { + // Get all custom field database tables which are assoicated with Volunteer Appeal. + $sql_query = "SELECT cg.table_name, cg.id as groupID, cg.is_multiple, cf.column_name, cf.id as fieldID, cf.data_type as fieldDataType FROM civicrm_custom_group cg, civicrm_custom_field cf WHERE cf.custom_group_id = cg.id AND cg.is_active = 1 AND cf.is_active = 1 AND cg.extends IN ( 'VolunteerAppeal' )"; + $dao10 = CRM_Core_DAO::executeQuery($sql_query); + // Join all custom field tables with appeal data which are assoicated with VolunteerAppeal. + while ($dao10->fetch()) { + $table_name = $dao10->table_name; + $column_name = $dao10->column_name; + $fieldID = $dao10->fieldID; + $table_alias = "table_".$fieldID; + // Join all custom field tables. + $join .= " LEFT JOIN $table_name $table_alias ON appeal.id = $table_alias.entity_id"; + $select .= ", ".$table_alias.".".$column_name; + foreach ($params["advanced_search"]["appealCustomFieldData"] as $key => $field_data) { + if(isset($field_data) && !empty($field_data)) { + $custom_field_array = explode("_", $key); + if(isset($custom_field_array) && isset($custom_field_array[1])) { + $custom_field_id = $custom_field_array[1]; + if($custom_field_id == $fieldID) { + // If value is in array then implode with Pipe first and then add in where condition. + if(is_array($field_data)) { + $field_data_string = implode("|", $field_data); + $where .= " AND CONCAT(',', $table_alias.$column_name, ',') REGEXP '$seperator($field_data_string)$seperator'"; + } else { + // Otherwise add with like query. + $where .= " AND $table_alias.$column_name Like'%$field_data%'"; + } + } + } + } + } + } + } + } + // Order by Logic. + $orderByColumn = "appeal.id"; + $order = "ASC"; + if(empty($params["orderby"])) { + $params["orderby"] = "upcoming_appeal"; + $params["order"] = "DESC"; + } + if(!empty($params["orderby"])) { + if($params["orderby"] == "project_beneficiary") { + $orderByColumn = "cc.display_name"; + } elseif($params["orderby"] == "upcoming_appeal") { + $select .= ", mdt.need_start_time"; + $join .= " LEFT JOIN (SELECT MIN(start_time) as need_start_time, id, project_id as need_project_id FROM civicrm_volunteer_need as need_sort Where id IS NOT NULL GROUP BY project_id) AS mdt ON (mdt.need_project_id = p.id)"; + $orderByColumn = "mdt.need_start_time"; + } else { + $orderByColumn = $params["orderby"]; + } + } + if(!empty($params["order"])) { + $order = $params["order"]; + } + // prepare orderby query. + $orderby = " GROUP By appeal.id ".$having." ORDER BY " . $orderByColumn . " " . $order; + + // Pagination Logic. + $no_of_records_per_page = 10;//2; + if(isset($params['page_no']) && !empty($params['page_no'])) { + $page_no = $params['page_no']; + } else { + $page_no = 1; + } + $offset = ($page_no-1) * $no_of_records_per_page; + $limit = " LIMIT ".$offset.", ".$no_of_records_per_page; + $sql = $select . $from . $join . $where . $orderby . $limit; + $dao = new CRM_Core_DAO(); + $dao->query($sql); + + $sql2 = $select . $from . $join . $where . $orderby; + $dao2 = new CRM_Core_DAO(); + $dao2->query($sql2); + $total_appeals = count($dao2->fetchAll()); + $appeals = array(); + $appeals['appeal'] = array(); + $appeal = []; + + // Get the global configuration. + $config = CRM_Core_Config::singleton(); + $upload_appeal_main_directory = $config->imageUploadDir.'appeal/main/'; + $upload_appeal_medium_directory = $config->imageUploadDir.'appeal/medium/'; + $upload_appeal_thumb_directory = $config->imageUploadDir.'appeal/thumb/'; + $default_image_name = "appeal-default-logo-sq.png"; + + // Prepare appeal details array with proper format. + while ($dao->fetch()) { + $appeal['id'] = $dao->id; + $appeal['project_id'] = $dao->project_id; + $appeal['title'] = $dao->title; + if($dao->image == "null" || !$dao->image) { + // check if the default image exists before we set the image property to it + if (file_exists($upload_appeal_main_directory . $default_image_name) + && file_exists($upload_appeal_medium_directory . $default_image_name) + && file_exists($upload_appeal_thumb_directory . $default_image_name) + ) { + $fetchedAppeal->image = $default_image_name; + } else { + $fetchedAppeal->image = null; + } + } + $appeal['image'] = $dao->image; + $appeal['appeal_teaser'] = $dao->appeal_teaser; + $appeal['appeal_description'] = htmlspecialchars_decode($dao->appeal_description); + $appeal['location_done_anywhere'] = $dao->location_done_anywhere; + $appeal['is_appeal_active'] = $dao->is_appeal_active; + $appeal['active_fromdate'] = $dao->active_fromdate; + $appeal['active_todate'] = $dao->active_todate; + $appeal['display_volunteer_shift'] = $dao->display_volunteer_shift; + $appeal['hide_appeal_volunteer_button'] = $dao->hide_appeal_volunteer_button; + $appeal['show_project_information'] = $dao->show_project_information; + $appeal['beneficiary_display_name'] = $dao->beneficiary_display_name; + $appeal['need_id'] = $dao->need_id; + $appeal['need_shift_id'] = $dao->need_shift_id; + $appeal['need_flexi_id'] = $dao->need_flexi_id; + if($params["orderby"] == "upcoming_appeal") { + $appeal['need_start_time'] = $dao->need_start_time; + } + // Prepare whole address of appeal in array. + $address = ""; + if ($dao->street_address) { + $address .= " ".$dao->street_address; + } + if ($dao->street_address && ($dao->city || $dao->postal_code)) { + $address .= '
'; + } + if ($dao->city) { + $address .= " ".$dao->city; + } + if ($dao->city && $dao->postal_code) { + $address .= ', '.$dao->postal_code; + } else if ($dao->postal_code) { + $address .= $dao->postal_code; + } + $appeal['location'] = $address; + $appeals['appeal'][] = $appeal; + } + $appeals['total_appeal'] = $total_appeals; + + return $appeals; + } + +} \ No newline at end of file diff --git a/CRM/Volunteer/DAO/VolunteerAppeal.php b/CRM/Volunteer/DAO/VolunteerAppeal.php new file mode 100644 index 00000000..42da815c --- /dev/null +++ b/CRM/Volunteer/DAO/VolunteerAppeal.php @@ -0,0 +1,389 @@ +__table = 'civicrm_volunteer_appeal'; + parent::__construct(); + } + + /** + * Returns foreign keys and entity references. + * + * @return array + * [CRM_Core_Reference_Interface] + */ + public static function getReferenceColumns() { + if (!isset(Civi::$statics[__CLASS__]['links'])) { + Civi::$statics[__CLASS__]['links'] = static ::createReferenceColumns(__CLASS__); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'project_id', 'civicrm_volunteer_project', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'loc_block_id', 'civicrm_loc_block', 'id'); + CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); + } + return Civi::$statics[__CLASS__]['links']; + } + + /** + * Returns all the column names of this table + * + * @return array + */ + public static function &fields() { + if (!isset(Civi::$statics[__CLASS__]['fields'])) { + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ + 'name' => 'id', + 'type' => CRM_Utils_Type::T_INT, + 'title' => ts('CiviVolunteer Appeal ID', array('domain' => 'org.civicrm.volunteer')) , + 'description' => CRM_Volunteer_ExtensionUtil::ts('Unique Volunteer Appeal ID'), + 'required' => TRUE, + 'table_name' => 'civicrm_volunteer_appeal', + 'entity' => 'VolunteerAppeal', + 'bao' => 'CRM_Volunteer_DAO_VolunteerAppeal', + 'localizable' => 0, + ], + 'project_id' => [ + 'name' => 'project_id', + 'type' => CRM_Utils_Type::T_INT, + 'title' => ts('CiviVolunteer Project ID') , + 'description' => CRM_Volunteer_ExtensionUtil::ts('Foreign key to the Volunteer Project for this record'), + 'required' => true, + 'table_name' => 'civicrm_volunteer_appeal', + 'entity' => 'VolunteerAppeal', + 'bao' => 'CRM_Volunteer_DAO_VolunteerAppeal', + 'FKClassName' => 'CRM_Volunteer_DAO_Project', + 'localizable' => 0, + ], + 'title' => [ + 'name' => 'title', + 'type' => CRM_Utils_Type::T_STRING, + 'title' => CRM_Volunteer_ExtensionUtil::ts('Opportunity Title'), + 'description' => CRM_Volunteer_ExtensionUtil::ts('The title of the Volunteer Opportunity'), + 'required' => true, + 'maxlength' => 255, + 'size' => CRM_Utils_Type::HUGE, + 'table_name' => 'civicrm_volunteer_appeal', + 'entity' => 'VolunteerAppeal', + 'bao' => 'CRM_Volunteer_DAO_VolunteerAppeal', + 'localizable' => 0, + ], + 'image' => [ + 'name' => 'image', + 'type' => CRM_Utils_Type::T_STRING, + 'title' => CRM_Volunteer_ExtensionUtil::ts('Opportunity Image'), + 'description' => CRM_Volunteer_ExtensionUtil::ts('The Image of the Volunteer Opportunity.'), + 'maxlength' => 255, + 'size' => CRM_Utils_Type::HUGE, + 'table_name' => 'civicrm_volunteer_appeal', + 'entity' => 'VolunteerAppeal', + 'bao' => 'CRM_Volunteer_DAO_VolunteerAppeal', + 'localizable' => 0, + ], + 'appeal_teaser' => [ + 'name' => 'appeal_teaser', + 'type' => CRM_Utils_Type::T_TEXT, + 'title' => CRM_Volunteer_ExtensionUtil::ts('Opportunity Teaser'), + 'description' => CRM_Volunteer_ExtensionUtil::ts('Opportunity Teaser'), + 'required' => false, + 'table_name' => 'civicrm_volunteer_appeal', + 'entity' => 'VolunteerAppeal', + 'bao' => 'CRM_Volunteer_DAO_VolunteerAppeal', + 'localizable' => 0, + ], + 'appeal_description' => [ + 'name' => 'appeal_description', + 'type' => CRM_Utils_Type::T_TEXT, + 'title' => CRM_Volunteer_ExtensionUtil::ts('Opportunity Description'), + 'description' => CRM_Volunteer_ExtensionUtil::ts('Full description of the Volunteer Appeal. Text and HTML allowed.'), + 'required' => false, + 'rows' => 8, + 'cols' => 60, + 'table_name' => 'civicrm_volunteer_appeal', + 'entity' => 'VolunteerAppeal', + 'bao' => 'CRM_Volunteer_DAO_VolunteerAppeal', + 'html' => array( + 'type' => 'RichTextEditor', + ), + 'localizable' => 0, + ], + 'loc_block_id' => array( + 'name' => 'loc_block_id', + 'type' => CRM_Utils_Type::T_INT, + 'title' => ts('Location Block ID') , + 'description' => 'FK to Location Block ID', + 'table_name' => 'civicrm_volunteer_appeal', + 'entity' => 'VolunteerAppeal', + 'bao' => 'CRM_Volunteer_DAO_VolunteerAppeal', + 'FKClassName' => 'CRM_Core_DAO_LocBlock', + ), + 'location_done_anywhere' => [ + 'name' => 'location_done_anywhere', + 'type' => CRM_Utils_Type::T_INT, + 'title' => CRM_Volunteer_ExtensionUtil::ts('Set Location done anywhere parameter for Volunteer Appeal.'), + 'description' => CRM_Volunteer_ExtensionUtil::ts('Location Done Anywhere "1" Means Location Done Anywhere True and "0" Means not true.'), + 'table_name' => 'civicrm_volunteer_appeal', + 'entity' => 'VolunteerAppeal', + 'bao' => 'CRM_Volunteer_DAO_VolunteerAppeal', + 'localizable' => 0, + ], + 'is_appeal_active' => [ + 'name' => 'is_appeal_active', + 'type' => CRM_Utils_Type::T_INT, + 'title' => CRM_Volunteer_ExtensionUtil::ts('Volunteer Opportunity Active Or Not'), + 'description' => CRM_Volunteer_ExtensionUtil::ts('Volunteer Opportunity is Active or not. '), + 'table_name' => 'civicrm_volunteer_appeal', + 'entity' => 'VolunteerAppeal', + 'bao' => 'CRM_Volunteer_DAO_VolunteerAppeal', + 'localizable' => 0, + ], + 'active_fromdate' => [ + 'name' => 'active_fromdate', + 'type' => CRM_Utils_Type::T_DATE, + 'title' => CRM_Volunteer_ExtensionUtil::ts('Active From Date for Opportunity'), + 'description' => CRM_Volunteer_ExtensionUtil::ts('Active From Date for Opportunity'), + 'table_name' => 'civicrm_volunteer_appeal', + 'entity' => 'VolunteerAppeal', + 'bao' => 'CRM_Volunteer_DAO_VolunteerAppeal', + 'localizable' => 0, + ], + 'active_todate' => [ + 'name' => 'active_todate', + 'type' => CRM_Utils_Type::T_DATE, + 'title' => CRM_Volunteer_ExtensionUtil::ts('Active To Date for Opportunity'), + 'description' => CRM_Volunteer_ExtensionUtil::ts('Active To Date for Opportunity'), + 'table_name' => 'civicrm_volunteer_appeal', + 'entity' => 'VolunteerAppeal', + 'bao' => 'CRM_Volunteer_DAO_VolunteerAppeal', + 'localizable' => 0, + ], + 'display_volunteer_shift' => [ + 'name' => 'display_volunteer_shift', + 'type' => CRM_Utils_Type::T_INT, + 'title' => CRM_Volunteer_ExtensionUtil::ts('Display Volunteer Shift or not.'), + 'description' => CRM_Volunteer_ExtensionUtil::ts('Display Volunteer Shift or not.'), + 'table_name' => 'civicrm_volunteer_appeal', + 'entity' => 'VolunteerAppeal', + 'bao' => 'CRM_Volunteer_DAO_VolunteerAppeal', + 'localizable' => 0, + ], + 'hide_appeal_volunteer_button' => [ + 'name' => 'hide_appeal_volunteer_button', + 'type' => CRM_Utils_Type::T_INT, + 'title' => CRM_Volunteer_ExtensionUtil::ts('Hide Volunteer Button or not.'), + 'description' => CRM_Volunteer_ExtensionUtil::ts('Hide Volunteer Button or not.'), + 'table_name' => 'civicrm_volunteer_appeal', + 'entity' => 'VolunteerAppeal', + 'bao' => 'CRM_Volunteer_DAO_VolunteerAppeal', + 'localizable' => 0, + ], + 'show_project_information' => [ + 'name' => 'show_project_information', + 'type' => CRM_Utils_Type::T_INT, + 'title' => CRM_Volunteer_ExtensionUtil::ts('Show Project Information.'), + 'description' => CRM_Volunteer_ExtensionUtil::ts('Show Project Information on Opportunity Detail Page.'), + 'table_name' => 'civicrm_volunteer_appeal', + 'entity' => 'VolunteerAppeal', + 'bao' => 'CRM_Volunteer_DAO_VolunteerAppeal', + 'localizable' => 0, + ], + ]; + CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); + } + return Civi::$statics[__CLASS__]['fields']; + } + + /** + * Return a mapping from field-name to the corresponding key (as used in fields()). + * + * @return array + * Array(string $name => string $uniqueName). + */ + public static function &fieldKeys() { + if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { + Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); + } + return Civi::$statics[__CLASS__]['fieldKeys']; + } + + /** + * Returns the names of this table + * + * @return string + */ + public static function getTableName() { + return self::$_tableName; + } + + /** + * Returns if this table needs to be logged + * + * @return bool + */ + public function getLog() { + return self::$_log; + } + + /** + * Returns the list of fields that can be imported + * + * @param bool $prefix + * + * @return array + */ + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'volunteer_appeal', $prefix, []); + return $r; + } + + /** + * Returns the list of fields that can be exported + * + * @param bool $prefix + * + * @return array + */ + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'volunteer_appeal', $prefix, []); + return $r; + } + + /** + * Returns the list of indices + * + * @param bool $localize + * + * @return array + */ + public static function indices($localize = TRUE) { + $indices = []; + return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; + } + +} diff --git a/CRM/Volunteer/Permission.php b/CRM/Volunteer/Permission.php index d216dac6..915fe6c7 100644 --- a/CRM/Volunteer/Permission.php +++ b/CRM/Volunteer/Permission.php @@ -1,5 +1,6 @@ installNeedMetaDateFields(); + // Volunteer Appeal table and relevant DB changes. + $this->schemaUpgradeVolunteerAppeal(); + // uncomment the next line to insert sample data // $this->executeSqlFile('sql/volunteer_sample.mysql'); } @@ -885,4 +888,25 @@ public static function checkExtensionDependencies() { } return $unmet; } -} + + /** + * CiviVolunteer 2302 update introduces Volunteer Appeal Functionality. + * + * @return boolean TRUE on success + */ + public function upgrade_2302() { + $this->ctx->log->info('Applying update 2302 - CiviVolunteer Volunteer Appeal Enhancement'); + $this->schemaUpgradeVolunteerAppeal(); + + return TRUE; + } + + /** + * Volunteer Appeal Functionality Integrated. + * Execute sql queries for relevant volunteer appeal functionality. + */ + public function schemaUpgradeVolunteerAppeal() { + $this->executeSqlFile('sql/volunteer_upgrade_with_appeal_2.1.sql'); + } + +} \ No newline at end of file diff --git a/CRM/Volunteer/Upgrader/Base.php b/CRM/Volunteer/Upgrader/Base.php index 7aba3c9c..5338d823 100644 --- a/CRM/Volunteer/Upgrader/Base.php +++ b/CRM/Volunteer/Upgrader/Base.php @@ -1,6 +1,7 @@ run($xml_file); return TRUE; @@ -281,7 +281,7 @@ private function deleteDeprecatedRevision() { // ******** Hook delegates ******** /** - * @see https://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_install + * @see https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_install */ public function onInstall() { $files = glob($this->extensionDir . '/sql/*_install.sql'); @@ -308,7 +308,7 @@ public function onInstall() { } /** - * @see https://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_postInstall + * @see https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_postInstall */ public function onPostInstall() { $revisions = $this->getRevisions(); @@ -321,7 +321,7 @@ public function onPostInstall() { } /** - * @see https://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_uninstall + * @see https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_uninstall */ public function onUninstall() { $files = glob($this->extensionDir . '/sql/*_uninstall.mysql.tpl'); @@ -342,7 +342,7 @@ public function onUninstall() { } /** - * @see https://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_enable + * @see https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_enable */ public function onEnable() { // stub for possible future use @@ -352,7 +352,7 @@ public function onEnable() { } /** - * @see https://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_disable + * @see https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_disable */ public function onDisable() { // stub for possible future use diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..4b4e2023 --- /dev/null +++ b/Makefile @@ -0,0 +1,7 @@ + +push: + gsync -ruvz ./ lce:/var/www/sandbox-volunteer.leadercenter.org/htdocs/sites/default/files/civicrm/ext/org.civicrm.volunteer/ + +push-utils: + gsync -ruvz ./create-custom-fields.php lce:/var/www/sandbox-volunteer.leadercenter.org/htdocs/ + gsync -ruvz ./volunteer_appeal_seed.php lce:/var/www/sandbox-volunteer.leadercenter.org/htdocs/ diff --git a/ang/volunteer.css b/ang/volunteer.css index c447ffae..e69609db 100644 --- a/ang/volunteer.css +++ b/ang/volunteer.css @@ -151,3 +151,350 @@ span.crm-button-text { #crm-vol-form-textarea-wrapper { clear: left; } + +.grid .grid2x2 { + min-height: 100%; + display: flex; + flex-wrap: wrap; + flex-direction: row; +} +.grid .grid2x2 > div { + display: flex; + flex-basis: calc(50% - 40px); + justify-content: center; + flex-direction: column; +} +/*.grid .grid2x2 > div > div { + display: flex; + justify-content: center; + flex-direction: row; +}*/ + +.grid .box { margin: 20px; } + +.grid .w-30{ + width: 30%; +} +.grid .w-70{ + width: 70%; +} +.text-right{ + text-align: right; +} +.text-center{ + text-align: center; +} +.detail-btn { + margin-right: 15px; + height: 37px; + width: 132px; +} +.p-l-15{ + padding-left: 15px; +} +.loc{ + padding-left: 15px; + padding-top: 15px; +} +.m-t-0{ + margin-top:0; +} +.clearfix{ + clear: both +} +.m-5{ + margin: 5px; +} +.text-limit { + width: 400px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} +.detail{ + flex-basis: auto!important; +} + +.beneficiary-details { + display: flex; + flex-direction: coloumn; + +} + +.beneficiary-details__image { + width: 120px; + margin-right: 20px; +} + +.appeal-details__image img, +.beneficiary-details__image img { + max-width: 100%; + height: auto; +} + +.beneficiary-details__info-detail { + margin-bottom: 20px; +} + +.search-box{position: relative;} +.search-box i{ +position: absolute; +top: 50%; +margin-top: -8px; +right: 10px; +opacity: 0.3; +} + +.modalDialog { + position: fixed; + font-family: Arial, Helvetica, sans-serif; + top: 0; + right: 0; + bottom: 0; + left: 0; + background: rgba(82, 79, 79, 0.8);/*rgba(0,0,0,0.8);*/ + z-index: 99999; + opacity:1; + -webkit-transition: opacity 400ms ease-in; + -moz-transition: opacity 400ms ease-in; + transition: opacity 400ms ease-in; + pointer-events: auto; +} +.modalDialog:target { + opacity:1; + pointer-events: auto; +} + +.modalDialog > div { + max-width: 720px; + position: relative; + margin: 10% auto; + padding: 0px 0px 10px 0px; + /*border-radius: 10px;*/ + border: black solid 1px; + background: #fff; + overflow: hidden; + overflow: auto; +} + +.modal-header { + border-bottom: 1px solid #9e9e9e; + padding: 0px 20px; +} + +.modal-header button{ + float: right; + margin-top: 5px; + margin-right: 3px; +} +.modal-header h2{ + margin-bottom: 10px !important; + margin-top: 5px; +} +.modalDialog > div input{ + height: 20px !important; +} + +.modalDialog > div input[type="text"] +{ + height: 20px !important; + width: 128px;/*pr*/ +} + +.filter-model{ + padding: 15px; +} + +.close { + background: #606061; + color: #FFFFFF; + line-height: 25px; + position: absolute; + right: -12px; + text-align: center; + top: -10px; + width: 24px; + text-decoration: none; + font-weight: bold; + -webkit-border-radius: 12px; + -moz-border-radius: 12px; + border-radius: 12px; + -moz-box-shadow: 1px 1px 3px #000; + -webkit-box-shadow: 1px 1px 3px #000; + box-shadow: 1px 1px 3px #000; +} + +.close:hover { background: #00d9ff; } + + +/*For tab*/ +* {box-sizing: border-box} +body {font-family: "Lato", sans-serif;} + +/* Style the tab */ +.tab { + float: left; + border: 1px solid #ccc; + background-color: #f1f1f1; + width: 30%; + height: 300px; + padding: 15px 10px; + overflow: auto; +} + +/* Style the buttons inside the tab */ +.tab button { + display: block; + background-color: inherit; + color: black; + padding: 14px 14px; + width: 100%; + border: #989393 solid 1px;/*border: none;*/ + outline: none; + text-align: left; + cursor: pointer; + transition: 0.3s; + font-size: 12px; + font-weight: normal; + /*font-size: 17px;*/ +} + +/* Change background color of buttons on hover */ +.tab button:hover { + background-color: #ddd; +} + +/* Create an active/current "tab button" class */ +.tab button.active { + background-color: #ccc; +} + +/* Style the tab content */ +.tabcontent { + float: left; + padding: 15px; + border: 1px solid #989393;/*#ccc;*/ + width: 70%; + border-left: none; + height: 300px; +} +.postal-code-input{ + width: 124px; +} +.posta-code-wrapper select{ + height: 27px; + padding: 0px !important; + font-size: 13px !important; +} +.posta-code-wrapper { + margin-top: 15px; +} +.show-opp input[type="checkbox"]{ + height: unset !important; +} +.modal-footer button{ + float: right; + margin-top: 10px; +} +.volunteer_appeal_result_count_message { + margin-right: 10px; + margin-left: 14px; + display: inline-block; +} +.manage_appeal_form .display_volunteer_note { + margin-left: 10px; + font-size: 0.8em; + font-weight: normal; + white-space: normal; + color: #696969; +} +.manage_appeal_form .location_done_anywhere { + margin-left: 30px; +} +.custom_fieldset_link .custom-fieldset-text { + color: #fff; + text-decoration: none; + margin-left: 5px; +} +.custom_fieldset_link { + display: inline-block; + width: 15%; + text-align: center; + margin-left: 10px !important; +} +.custom_fieldgroup_dropdown { + display: inline-block; + height: 30px; + margin-left: 14px; + font-size: 14px !important; + padding: 2px !important; + cursor: pointer; +} +.crm-button-remove-custom-group { + float: right; + margin-right: 20px; +} + +.sortorder:after { + content: '\25b2'; /* BLACK UP-POINTING TRIANGLE */ +} +.sortorder.reverse:after { + content: '\25bc'; /* BLACK DOWN-POINTING TRIANGLE */ +} + +.sortorder.reverseExpiredTable:after { + content: '\25bc'; /* BLACK DOWN-POINTING TRIANGLE */ +} +.delete_appeal, .edit_appeal { + cursor: pointer; +} +.custom_fieldset_selector .custom_hr { + border: 1px lightgrey; + margin: 20px; +} +.grid_active { + background: #878484; + border: 1px solid #878484; +} +.advance_filter_wrapper { + display: inline-block; +} + +.advance_filter_footer { + position: absolute; + bottom: 0; + right: 0; + background: #fff; + left: 0; + box-shadow: 4px -1px 6px 0px rgba(0, 0, 0, 0.3); + padding: 5px 15px 15px 10px; +} +.advance_filter_body { + height: 420px; +} +.custom_fied_content { + overflow: auto; +} +.reset_filter { + display: inline-block; +} +#select2-drop { + z-index: 99999; +} +.active_filters { + font-size: 16px; + margin-bottom: 10px; + border: 1px solid #d3d3d3; + padding: 5px; + border-radius: 4px; +} +.active_filter_content { + margin-top: 10px; +} +.active_filter_heading { + text-decoration: underline; + font-size: 18px; +} +.filter_data { + margin-bottom: 4px; +} \ No newline at end of file diff --git a/ang/volunteer.js b/ang/volunteer.js index 8f8ff5be..9b8ef168 100644 --- a/ang/volunteer.js +++ b/ang/volunteer.js @@ -28,6 +28,21 @@ CRM.$('#crm-main-content-wrapper').block(); }) + .filter('plainText', function() { + return function(textish) { + return angular.element(textish).text(); + }; + }) + .filter('textShortenerFilter', function() { + return function(text, length) { + if (text.length > length) { + text = text ? String(text).replace(/<[^>]+>/gm, '') : ''; + return text.substr(0, length) + "..."; + } + return text; + } + }) + .factory('volOppSearch', ['crmApi', '$location', '$route', function(crmApi, $location, $route) { //Search params and results are stored here and assigned by reference to the form var volOppSearch = {}; @@ -165,6 +180,100 @@ }) + // Example: + // Display a location block. In the example above, myLocObj should match the + // format of an item in the values array of api.VolunteerProject.getlocblockdata. + .directive('crmVolLocBlock', function() { + return { + restrict: 'E', + controller: ['$scope', function($scope) { + $scope.$watch('loc_block', function (newValue, oldValue, scope) { + $scope.cntAddressParts = _.size(newValue); + }, true); + }], + scope: { + heading: '=', + loc_block: '=data' + }, + templateUrl: '~/volunteer/shared/crmVolLocBlockView.html' + }; + }) + + + // Example: + // Provides a detail view for a volunteer project. locBlockHeading is passed + // through to crmVolLocBlock for displaying a heading for the address. + .directive('crmVolProjectDetail', function() { + return { + link: function(scope, element, attrs) { + scope.ts = CRM.ts(null); + }, + restrict: 'E', + scope: { + locBlockHeading: '=', + project: '=data' + }, + templateUrl: '~/volunteer/shared/crmVolProjectDetailView.html' + }; + }) + + + // Example: + // Provides a thumbnail view for a volunteer project. + .directive('crmVolProjectThumb', function() { + return { + restrict: 'E', + scope: { + project: '=data' + }, + templateUrl: '~/volunteer/shared/crmVolProjectThumbView.html' + }; + }) + + + // Example: + // Builds a table row with fields for updating time entries. In the example above, + // entry should be in the format of a value from api.VolunteerAssignments.get. + .directive('crmVolTimeEntry', function() { + return { + link: function(scope, element, attrs) { + scope.ts = CRM.ts(null); + + // Remove a row when its button is clicked. + element.find('button[crm-icon="fa-times"]').click(function () { + var index = scope.$parent.$index; + scope.$parent.$parent.ngModel.splice(index, 1); + scope.$apply(); + }); + }, + replace: true, + require: ['ngModel'], + restrict: 'AC', + scope: { + ngModel: '=' + }, + templateUrl: '~/volunteer/shared/crmVolTimeEntryView.html' + }; + }) + + + // Example: + // Builds a table for creating time entries. See crmVolTimeEntry. + .directive('crmVolTimeTable', function() { + return { + link: function(scope, element, attrs) { + scope.ts = CRM.ts(null); + }, + require: ['ngModel'], + restrict: 'E', + scope: { + ngModel: '=' + }, + templateUrl: '~/volunteer/shared/crmVolTimeTableView.html' + }; + }) + + /** * This is a service for loading the backbone-based volunteer UIs (and their * prerequisite scripts) into angular routes. @@ -251,18 +360,18 @@ // TODO: Figure out a more authoritative way to check this, rather than // simply setting and checking a flag. function verifyScripts() { - return !!CRM.volunteerBackboneScripts; + return Boolean(CRM.volunteerBackboneScripts); } function verifyTemplates() { return (angular.element("#volunteer_backbone_templates div").length > 0); } function verifySettings() { - return !!CRM.volunteerBackboneSettings; + return Boolean(CRM.volunteerBackboneSettings); } return { verify: function() { - return (!!window.Backbone && verifyScripts() && verifySettings() && verifyTemplates()); + return (Boolean(window.Backbone) && verifyScripts() && verifySettings() && verifyTemplates()); }, load: function() { var deferred = $q.defer(); diff --git a/ang/volunteer/Appeal.html b/ang/volunteer/Appeal.html new file mode 100644 index 00000000..0518cda9 --- /dev/null +++ b/ang/volunteer/Appeal.html @@ -0,0 +1,160 @@ +
+
+
+
+
+
+
+
+
+
+ +
+
+

{{appeal.action}} {{ts(" Opportunity for")}} {{project.title}}

+
+ {{ts("Opportunity Settings")}} +
+ +
+
+ + Opportunity Image +
+
+ +
+ +
+
+ +
+
+ +
+
+ + + +
+
+
+ +
+ +
+
{{ts("This location is used by other events/appeals. Modifying location information will change values for all events/appeals")}}
+
+ {{ts("Address")}} +
+ +
+
+ +
+
+ +
+
+ +
+
+ + - + +
+
+ +
+
+
+ {{ts("Phone")}} +
+ + {{ts("ext.")}} + +
+
+ + {{ts("ext.")}} + +
+
+
+ {{ts("Email")}} +
+ +
+
+ +
+
+
+ + +
+
+ +
+
+ + + +
+
+ + Check this box if you want volunteers to be able to sign up for shifts for your volunteer project +
+
+ + {{ ts("Checking this box hides this Opportunity's 'Volunteer' button on the appeal detail view. Useful when Display Volunteer Shift is enabled and you wish for volunteers to signup for specific shifts.") }} +
+
+ + {{ ts("Select this option if you would like to display information from the Project this opportunity belongs to. The Project title and description will be displayed at the end of the opportunity.") }} +
+
+ +
+ + + {{saveAndNextLabel}} + + + + {{ts('Save and Done')}} + + + + {{ts('Cancel')}} + +
+ +
+ +
diff --git a/ang/volunteer/Appeal.js b/ang/volunteer/Appeal.js new file mode 100644 index 00000000..7b1ec8b3 --- /dev/null +++ b/ang/volunteer/Appeal.js @@ -0,0 +1,539 @@ +(function(angular, $, _) { + + angular.module('volunteer').config(function($routeProvider) { + $routeProvider.when('/volunteer/manage_appeal/:projectId/:appealId', { + controller: 'VolunteerCreateAppeal', + templateUrl: '~/volunteer/Appeal.html', + resolve: { + countries: function(crmApi) { + return crmApi('VolunteerUtil', 'getcountries', {}).then(function(result) { + return result.values; + }); + }, + appeal: function(crmApi, $route) { + if ($route.current.params.appealId == 0) { + return { + id: 0 + }; + } else { + return crmApi('VolunteerAppeal', 'getsingle', { + id: $route.current.params.appealId, + projectId: $route.current.params.projectId, + return: ['custom'] + }).then( + // success + null, + // error + function () { + CRM.alert( + ts('No volunteer appeal exists with an ID of %1', {1: $route.current.params.appealId}), + ts('Not Found'), + 'error' + ); + } + ); + } + }, + project: function(crmApi, $route) { + return crmApi('VolunteerProject', 'getsingle', { + id: $route.current.params.projectId, + return: ['custom'] + }).then( + // success + null, + // error + function () { + CRM.alert( + ts('No volunteer project exists with an ID of %1', {1: $route.current.params.projectId}), + ts('Not Found'), + 'error' + ); + } + ); + }, + supporting_data: function(crmApi, $route) { + return crmApi('VolunteerUtil', 'getsupportingdata', { + controller: 'VolunteerAppeal', + appeal_id: $route.current.params.appealId, + project_id: $route.current.params.projectId + }); + }, + custom_fieldset_volunteer: function(crmApi) { + return crmApi('VolunteerAppeal', 'getCustomFieldsetVolunteer', { + controller: 'VolunteerAppeal' + }); + }, + location_blocks: function(crmApi) { + return crmApi('VolunteerProject', 'locations', {}); + } + } + }); + } + ); + + angular.module('volunteer').controller('VolunteerCreateAppeal', function($scope, $sce, $location, $q, $route, crmApi, crmUiAlert, crmUiHelp, countries, appeal, project, supporting_data, location_blocks, volBackbone, custom_fieldset_volunteer, $compile) { + + /** + * We use custom "dirty" logic rather than rely on Angular's native + * functionality because we need to make a separate API call to + * create/update the locBlock object (a distinct entity from the project) + * if any of the locBlock fields have changed, regardless of whether other + * form elements are dirty. + */ + $scope.locBlockIsDirty = false; + + /** + * This flag allows the code to distinguish between user- and + * server-initiated changes to the locBlock fields. Without this flag, the + * changes made to the locBlock fields when a location is fetched from the + * server would cause the watch function to mark the locBlock dirty. + */ + $scope.locBlockSkipDirtyCheck = false; + + // The ts() and hs() functions help load strings for this module. + var ts = $scope.ts = CRM.ts('org.civicrm.volunteer'); + var hs = $scope.hs = crmUiHelp({file: 'CRM/Volunteer/Form/Volunteer'}); // See: templates/CRM/volunteer/Project.hlp + + /** + * Set form default value when create appeal or edit appeal page is open. + */ + setFormDefaults = function() { + if(appeal.id == 0) { + // When creating: location value defaults to project location. + appeal.loc_block_id = project.loc_block_id; + // Is Appeal Active Checkbox by default set as 1. + appeal.is_appeal_active = 1; + + today = new Date(); + future = new Date(today.getFullYear(), today.getMonth() + 3, today.getDate()); + appeal.active_fromdate = today.getFullYear() + "-" + today.getMonth() + "-" +today.getDate(); + appeal.active_todate = future.getFullYear() + "-" + future.getMonth() + "-" + future.getDate(); + } + // Get project Id from url. + appeal.project_id = $route.current.params.projectId; + }; + + // Set Default Form Data when page is loaded. + setFormDefaults(); + + if (CRM.vars['org.civicrm.volunteer'] && CRM.vars['org.civicrm.volunteer'].context) { + $scope.formContext = CRM.vars['org.civicrm.volunteer'].context; + } else { + $scope.formContext = 'standAlone'; + } + + switch ($scope.formContext) { + case 'eventTab': + volBackbone.load(); + var cancelCallback = function (projectId) { + CRM.$("body").trigger("volunteerProjectCancel"); + }; + $scope.saveAndNextLabel = ts('Save'); + break; + + default: + var cancelCallback = function (projectId) { + $location.path("/volunteer/project_appeals/"+$scope.appeal.project_id); + }; + $scope.saveAndNextLabel = ts('Save and Add Another'); + } + $scope.project = project; + $scope.countries = countries; + $scope.locationBlocks = location_blocks.values; + $scope.locationBlocks[0] = "Create a new Location"; + $scope.locBlock = {}; + appeal.is_appeal_active = (appeal.is_appeal_active == "1"); + appeal.show_project_information = (appeal.show_project_information == "1"); + appeal.display_volunteer_shift = (appeal.display_volunteer_shift == "1"); + appeal.hide_appeal_volunteer_button = (appeal.hide_appeal_volunteer_button == "1"); + $scope.custom_fieldset_group = custom_fieldset_volunteer.values; + $scope.supporting_data = supporting_data.values; + // Manage action of appeal form. + if(appeal.id == 0) { + appeal.action = "Create"; + $scope.supporting_data.appeal_custom_field_groups = []; + } else { + appeal.action = "Edit"; + if(appeal.location_done_anywhere == 1) { + appeal.location_done_anywhere = true; + } else { + appeal.location_done_anywhere = false; + } + delete appeal.contact_id; + if(appeal.image == "appeal-default-logo-sq.png") { + appeal.image = ""; + } + appeal.old_image = appeal.image; + + /* + * Set default value of custom group dropdown when edit appeal page open. + * remove custom group from dropdown if user have already set any custom group. + */ + setTimeout(function() { + var available_custom_fieldsets = []; + for (var key in supporting_data.values.appeal_custom_field_groups) { + var group_id = supporting_data.values.appeal_custom_field_groups[key].collectionId; + available_custom_fieldsets['custom_'+group_id] = group_id; + // Manage delete icon for specific custom field group. + var group_name = supporting_data.values.appeal_custom_field_groups[key].name; + var group_title = supporting_data.values.appeal_custom_field_groups[key].title; + // Prepare delete icon for added custom group. + var deleteCustomGroupHtml = ''; + var compiledHtml = $compile(deleteCustomGroupHtml)($scope); + angular.element(document.getElementsByClassName('crmRenderFieldCollection-'+group_name)).prepend(compiledHtml); + // Add Click Eventlistner for delete icon of custom group and apply that in DOM. + document.getElementById("customGroupId_"+group_id).addEventListener('click', function(event) { + $scope.$apply(function() { + var targetElement = event.target || event.srcElement; + // Add deleted custom field group in dropdown for adding again. + var custom_group_id = CRM.$(targetElement).data("group"); + var grouptitle = CRM.$(targetElement).data("grouptitle"); + if(grouptitle && custom_group_id) { + $scope.custom_fieldset_group[custom_group_id] = {id:custom_group_id, title:grouptitle}; + } + // Remove custom field group from UI. + for (var key in $scope.supporting_data.appeal_custom_field_groups) { + // check if the property/key is defined in the object itself, not in parent + if ($scope.supporting_data.appeal_custom_field_groups.hasOwnProperty(key)) { + if(custom_group_id == $scope.supporting_data.appeal_custom_field_groups[key].collectionId) { + // Unset selected field when delete group called. + var custom_field_selected_keys = Object.keys($scope.supporting_data.appeal_custom_field_groups[key].fields); + for (var obj_key in custom_field_selected_keys) { + var custom_field_value = custom_field_selected_keys[obj_key]; + // Unset data for that value when delete group icon clicked. + $scope.appeal[custom_field_value] = ""; + } + // Remove custom field group from UI. + $scope.supporting_data.appeal_custom_field_groups.splice(key, 1); + } + } + } + }); + }); + } + // Remove custom field set from dropdown which were set already. + for (var key in custom_fieldset_volunteer.values) { + var custom_field_id_exist = custom_fieldset_volunteer.values[key].id; + if(available_custom_fieldsets['custom_'+custom_field_id_exist]) { + delete $scope.custom_fieldset_group[custom_field_id_exist]; + } + } + $scope.$digest(); + }, 1000); + } + $scope.relationship_types = supporting_data.values.relationship_types; + $scope.phone_types = supporting_data.values.phone_types; + $scope.appeal = appeal; + $scope.showProfileBlock = CRM.checkPerm('edit volunteer registration profiles'); + $scope.showRelationshipBlock = CRM.checkPerm('edit volunteer project relationships'); + + /** + * Populates locBlock fields based on user selection of location. + * + * Makes an API request. + * + * @see $scope.locBlockIsDirty + * @see $scope.locBlockSkipDirtyCheck + */ + $scope.refreshLocBlock = function() { + if (Boolean($scope.appeal.loc_block_id)) { + crmApi("VolunteerProject", "getlocblockdata", { + "return": "all", + "sequential": 1, + "id": $scope.appeal.loc_block_id + }).then(function(result) { + if(!result.is_error) { + $scope.locBlockSkipDirtyCheck = true; + $scope.locBlock = result.values[0]; + $scope.locBlockIsDirty = false; + } else { + CRM.alert(result.error); + } + }); + } + }; + //Refresh as soon as we are up and running because we don't have this data yet. + $scope.refreshLocBlock(); + + /** + * If the user selects the option to create a new locBlock (id = 0), set + * some defaults and display the necessary fields. Otherwise, fetch the + * location data so we can display it for editing. + */ + $scope.$watch('appeal.loc_block_id', function (newValue) { + if (newValue == 0) { + $scope.locBlock = { + address: { + country_id: _.findWhere(countries, {is_default: "1"}).id + } + }; + + $("#crm-vol-location-block .crm-accordion-body").slideDown({complete: function() { + $("#crm-vol-location-block .crm-accordion-wrapper").removeClass("collapsed"); + }}); + } else { + //Load the data from the server. + $scope.refreshLocBlock(); + } + }); + + /** + * @see $scope.locBlockIsDirty + * @see $scope.locBlockSkipDirtyCheck + */ + $scope.$watch('locBlock', function(newValue, oldValue) { + if ($scope.locBlockSkipDirtyCheck) { + $scope.locBlockSkipDirtyCheck = false; + } else { + $scope.locBlockIsDirty = true; + } + }, true); + + /** + * Validate form field when appeal form submit. + * Validate required field validation for some of the fields. + */ + $scope.validate = function() { + var valid = true; + + if(!$scope.appeal.title) { + CRM.alert(ts("Title is a required field"), "Required"); + valid = false; + } + if(!$scope.appeal.appeal_description) { + CRM.alert(ts("Opportunity Description is a required field"), "Required"); + valid = false; + } + if($scope.locBlockIsDirty && !$scope.appeal.loc_block_id && !appeal.location_done_anywhere) { + CRM.alert(ts("Opportunity Location is a required field"), "Required"); + valid = false; + } + + valid = (valid); + + return valid; + }; + + + /** + * Helper function which serves as a harness for the API calls which + * constitute form submission. + * + * TODO: The value of loc_block_id is a little too magical. "" means the + * location is empty. "0" means the location is new, i.e., about to be + * created. Any other int-like string represents the ID of an existing + * location. This magic could perhaps be encapsulated in a function whose + * job it is to return an operation: "create" or "update." + * + * @returns {Mixed} Returns project ID on success, boolean FALSE on failure. + */ + doSave = function() { + if ($scope.validate()) { + // When the loc block ID is an empty string, it indicates that the + // location is blank. Thus, there is no loc block to create/edit. + if ($scope.locBlockIsDirty && $scope.appeal.loc_block_id !== "") { + // pass an ID only if we are updating an existing locblock + $scope.locBlock.id = $scope.appeal.loc_block_id === "0" ? null : $scope.appeal.loc_block_id; + return crmApi('VolunteerProject', 'savelocblock', $scope.locBlock).then( + // success + function (result) { + $scope.appeal.loc_block_id = result.id; + return _saveAppeal(); + }, + // failure + function(result) { + crmUiAlert({text: ts('Failed to save location details. Appeal could not be saved.'), title: ts('Error'), type: 'error'}); + console.log('api.VolunteerProject.savelocblock failed with the following message: ' + result.error_message); + } + ); + } else { + return _saveAppeal(); + } + } else { + return $q.reject(false); + } + }; + + /** + * Helper function which saves a volunteer project appeal. + * + * @returns {Mixed} Returns appeal ID on success. + */ + _saveAppeal = function() { + // Zero is a bit of a magic number the form uses to connote creation of + // a new location; this value should never be passed to the API. + if ($scope.appeal.loc_block_id === "0") { + delete $scope.appeal.loc_block_id; + } + // Set the value of parameter based on selection. + appeal.image = $scope.appeal.image; + appeal.is_appeal_active = $scope.appeal.is_appeal_active; + appeal.display_volunteer_shift = $scope.appeal.display_volunteer_shift; + appeal.hide_appeal_volunteer_button = $scope.appeal.hide_appeal_volunteer_button; + if(!appeal.location_done_anywhere) { + appeal.location_done_anywhere = 0; + } else { + appeal.location_done_anywhere = 1; + appeal.loc_block_id = ""; + } + if(!appeal.is_appeal_active) { + appeal.is_appeal_active = 0; + } else { + appeal.is_appeal_active = 1; + } + if(!appeal.display_volunteer_shift) { + appeal.display_volunteer_shift = 0; + } else { + appeal.display_volunteer_shift = 1; + } + if(!appeal.hide_appeal_volunteer_button) { + appeal.hide_appeal_volunteer_button = 0; + } else { + appeal.hide_appeal_volunteer_button = 1; + } + if(!appeal.show_project_information) { + appeal.show_project_information = 0; + } else { + appeal.show_project_information = 1; + } + return crmApi('VolunteerAppeal', 'create', $scope.appeal).then( + function(success) { + return success.values.id; + }, + function(fail) { + var text = ts('Your submission was not saved. Resubmitting the form is unlikely to resolve this problem. Please contact a system administrator.'); + var title = ts('A technical problem has occurred'); + crmUiAlert({text: text, title: title, type: 'error'}); + console.log('api.VolunteerProject.create failed with the following message: ' + fail.error_message); + } + ); + }; + + /** + * This function is used for save appeal and then go back to listing page of appeals. + */ + $scope.saveAndDone = function () { + doSave().then(function (appealId) { + if (appealId) { + crmUiAlert({text: ts('Changes saved successfully'), title: ts('Saved'), type: 'success'}); + $location.path("/volunteer/project_appeals/"+$scope.appeal.project_id); + } + }); + }; + + /** + * This function is used for save appeal and then go back to same page for adding new appeal for specific project. + */ + $scope.saveAndNext = function() { + doSave().then(function(appealId) { + if (appealId) { + crmUiAlert({text: ts('Changes saved successfully'), title: ts('Saved'), type: 'success'}); + $location.path("/volunteer/manage_appeal/" + $scope.appeal.project_id + "/0"); + } + }); + }; + + $scope.cancel = function() { + cancelCallback(); + }; + + //Handle Refresh requests + CRM.$("body").on("volunteerProjectRefresh", function() { + $route.reload(); + }); + + /* + * This function is used for manage uplaod image of appeal. + * First its convert into base64 and pass that string into api. + */ + $scope.uploadFile = function (e) { + + var file = e.target.files[0]; + var reader = new FileReader(); + reader.onloadend = function () { + $scope.appeal.image_data = reader.result; + } + reader.readAsDataURL(file); + $scope.appeal.image = ""; + if(e.target.files[0]) { + var file_name = e.target.files[0].name; + $scope.appeal.image = file_name; + } + }; + + /* + * This function is used for display selected custom group. + * Makes an API request. + * add that group in appeal_custom_field_groups object. + */ + $scope.customFieldSetDisplay = function() { + let item = $scope.customFieldSetSelected; + if(item) { + // Get the custom group id and title. + var custom_fieldset_id = item.id; + var custom_fieldset_title = item.title; + // Fetch new custom group detail based on that ID and display in UI. + crmApi("VolunteerUtil", "getsupportingdata", { + controller: 'VolunteerAppeal', + custom_fieldset_id : custom_fieldset_id + }).then(function(result) { + if(!result.is_error) { + // Add New Customfield group in UI. + $scope.customFieldSetData = result.values.appeal_custom_field_groups; + if(result.values.appeal_custom_field_groups[0]) { + + $scope.supporting_data.appeal_custom_field_groups.push(result.values.appeal_custom_field_groups[0]); + + // Delete selected custom group from dropdown. + delete $scope.custom_fieldset_group[custom_fieldset_id]; + + // Add Delete Icon for remove selected custom group and manage delete action for that. + setTimeout(function() { + // Manage delete icon for specific custom field group. + var group_name = result.values.appeal_custom_field_groups[0].name; + var group_title = result.values.appeal_custom_field_groups[0].title; + // Prepare delete icon for added custom group. + var deleteCustomGroupHtml = ''; + var compiledHtml = $compile(deleteCustomGroupHtml)($scope); + angular.element(document.getElementsByClassName('crmRenderFieldCollection-'+group_name)).prepend(compiledHtml); + + // Add Click Eventlistner for delete icon of custom group and apply that in DOM. + document.getElementById("customGroupId_"+custom_fieldset_id).addEventListener('click', function(event) { + $scope.$apply(function() { + var targetElement = event.target || event.srcElement; + // Add deleted custom field group in dropdown for adding again. + var custom_group_id = CRM.$(targetElement).data("group"); + var grouptitle = CRM.$(targetElement).data("grouptitle"); + if(custom_group_id && grouptitle) { + $scope.custom_fieldset_group[custom_group_id] = {id:custom_group_id, title:grouptitle}; + } + // Remove custom field group from UI. + for (var key in $scope.supporting_data.appeal_custom_field_groups) { + // check if the property/key is defined in the object itself, not in parent + if ($scope.supporting_data.appeal_custom_field_groups.hasOwnProperty(key)) { + if(custom_group_id == $scope.supporting_data.appeal_custom_field_groups[key].collectionId) { + var custom_field_selected_keys = Object.keys($scope.supporting_data.appeal_custom_field_groups[key].fields); + // Unset data for that value when delete group icon clicked. + for (var obj_key in custom_field_selected_keys) { + var custom_field_value = custom_field_selected_keys[obj_key]; + // Unset data for that value when delete group icon clicked. + $scope.appeal[custom_field_value] = ""; + } + // Remove custom field group from UI. + $scope.supporting_data.appeal_custom_field_groups.splice(key, 1); + } + } + } + }); + }); + $scope.$digest(); + }, 1000); + } + } else { + CRM.alert(result.error); + } + }); + } + }; + }); +})(angular, CRM.$, CRM._); \ No newline at end of file diff --git a/ang/volunteer/AppealDetail.html b/ang/volunteer/AppealDetail.html new file mode 100644 index 00000000..a2627444 --- /dev/null +++ b/ang/volunteer/AppealDetail.html @@ -0,0 +1,85 @@ + + +
+ +
+
+
+
+
+ Opportunity Image +
+
+
+

{{ appeal.title }}

+
+

+
+
+
+ + +
+
+

Location

+

+
+
+

Available Shifts

+ + +
{{shift}}
+
+ +
+ +
+
+
+

+

{{appeal.project.title}}

+

+
+
+ +
+
+
+ {{beneficiary.display_name}} +

{{beneficiary.email}}

+
+ +
+
+ +
+
+ +
+
+
+
+
\ No newline at end of file diff --git a/ang/volunteer/AppealDetail.js b/ang/volunteer/AppealDetail.js new file mode 100644 index 00000000..0c05c7a0 --- /dev/null +++ b/ang/volunteer/AppealDetail.js @@ -0,0 +1,55 @@ +(function(angular, $, _) { + + angular.module('volunteer').config(function($routeProvider) { + $routeProvider.when('/volunteer/appeal/:appealId', { + controller: 'VolunteerAppealDetail', + templateUrl: '~/volunteer/AppealDetail.html', + resolve: { + projectAppealsData: function(crmApi, $route) { + return crmApi('VolunteerAppeal', 'getAppealData', { + id: $route.current.params.appealId + }).then(function (data) { + appeal = data.values; + return appeal; + },function(error) { + if (error.is_error) { + CRM.alert(error.error_message, ts("Error"), "error"); + } else { + return error; + } + }); + }, + supporting_data: function(crmApi, $route) { + return crmApi('VolunteerUtil', 'getsupportingdata', { + controller: 'VolunteerAppealDetail', + appeal_id: $route.current.params.appealId + }); + }, + } + }); + }); + + angular.module('volunteer').controller('VolunteerAppealDetail', function ($scope, crmApi, projectAppealsData, supporting_data, $window, $location) { + if (!$window.location.origin) { + $window.location.origin = $window.location.protocol + "//" + + $window.location.hostname + + ($window.location.port ? ':' + $window.location.port : ''); + } + var ts = $scope.ts = CRM.ts('org.civicrm.volunteer'); + $scope.appeal = appeal; + $scope.showShift = parseInt(appeal.display_volunteer_shift); + $scope.locAny = parseInt(appeal.location_done_anywhere); + $scope.showVolunteer = parseInt(appeal.hide_appeal_volunteer_button); + $scope.supporting_data = supporting_data.values; + $scope.redirectTo=function(projectId) { + $location.path("/volunteer/opportunities?project="+projectId+"&hideSearch=1"); + } + // Redirect Back to Search Appeal Page. + $scope.backToSearchAppeal=function() { + $location.path("/volunteer/appeals"); + } + $scope.volSignup= function(need_flexi_id,projectId) { + $window.location.href = CRM.url("civicrm/volunteer/signup", "reset=1&needs[]="+need_flexi_id+"&dest=list"); + } + }); +})(angular, CRM.$, CRM._); \ No newline at end of file diff --git a/ang/volunteer/AppealGrid.html b/ang/volunteer/AppealGrid.html new file mode 100644 index 00000000..f1c08ee0 --- /dev/null +++ b/ang/volunteer/AppealGrid.html @@ -0,0 +1,26 @@ +
+
+
+
+
+ Opportunity Image +
+
+
+

{{ appeal.title }}

+ {{appeal.beneficiary_display_name}} +

{{ appeal.appeal_teaser }}

+

+
+
+
+
+ + +
+
+
+
+
\ No newline at end of file diff --git a/ang/volunteer/AppealList.html b/ang/volunteer/AppealList.html new file mode 100644 index 00000000..9efba6b7 --- /dev/null +++ b/ang/volunteer/AppealList.html @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ang/volunteer/Appeals.html b/ang/volunteer/Appeals.html new file mode 100644 index 00000000..597412f3 --- /dev/null +++ b/ang/volunteer/Appeals.html @@ -0,0 +1,93 @@ +
+
+
+
+
+
+
+
+
+
{{ts('Add an Opportunity')}}


+ +
+

{{ts("Active Opportunities")}}

+ + + + + + + + + + + + + + + + + + + + +
+
+
+
+

{{ts("Expired Opportunities")}}

+ + + + + + + + + + + + + + + + + + +
+
+
\ No newline at end of file diff --git a/ang/volunteer/Appeals.js b/ang/volunteer/Appeals.js new file mode 100644 index 00000000..39db45ca --- /dev/null +++ b/ang/volunteer/Appeals.js @@ -0,0 +1,172 @@ +(function(angular, $, _) { + + angular.module('volunteer').config(function($routeProvider) { + $routeProvider.when('/volunteer/project_appeals/:projectId', { + controller: 'VolunteerAppeal', + templateUrl: '~/volunteer/Appeals.html', + resolve: { + projectAppealsData: function(crmApi, $route) { + return crmApi('VolunteerAppeal', 'get', { + project_id: $route.current.params.projectId, + }).then(function (data) { + let projectAppeals=[]; + for(let key in data.values) { + projectAppeals.push(data.values[key]); + } + return projectAppeals; + },function(error) { + if (error.is_error) { + CRM.alert(error.error_message, ts("Error"), "error"); + } else { + return error; + } + }); + } + } + }); + } + ); + + angular.module('volunteer').controller('VolunteerAppeal', function ($scope,crmApi,crmUiAlert,$route, projectAppealsData) { + var ts = $scope.ts = CRM.ts('org.civicrm.volunteer'); + //assign project id from url. + $scope.projectId = $route.current.params.projectId; + // Get current date for filter data for expired appeal and active appeal. + var today = new Date(); + var dd = String(today.getDate()).padStart(2, '0'); + var mm = String(today.getMonth() + 1).padStart(2, '0'); //January is 0! + var yyyy = today.getFullYear(); + // today_date contains date with 2019-06-13 format. + $scope.today_date = yyyy + '-' + mm + '-' + dd; + + /* + * Manage code for Active appeal table. + **/ + $scope.propertyName = 'active_todate'; + $scope.reverse = false; + $scope.appeals = projectAppealsData; + + // Sort function for active appeal table. + $scope.sortBy = function(propertyName) { + $scope.reverse = ($scope.propertyName === propertyName) ? !$scope.reverse : false; + $scope.propertyName = propertyName; + }; + + /* + * Manage code for expired appeal table. + **/ + $scope.propertyNameExpiredTable = 'active_todate'; + $scope.reverseExpiredTable = false; + $scope.expired_appeals = projectAppealsData; + + // Sort function for expired appeal table. + $scope.sortByExpiredTable = function(propertyNameExpiredTable) { + $scope.reverseExpiredTable = ($scope.propertyNameExpiredTable === propertyNameExpiredTable) ? !$scope.reverseExpiredTable : false; + $scope.propertyNameExpiredTable = propertyNameExpiredTable; + }; + + /* + * Update Appeal status. + * Set appeal status like is active or not. + * + **/ + $scope.updateAppeal = function ($event, id, project_id) { + var checkbox = $event.target; + var appeal = {}; + appeal.id = id; + if(checkbox.checked) { + appeal.is_appeal_active = 1; + } else { + appeal.is_appeal_active = 0; + } + appeal.project_id = project_id; + var appealId = crmApi('VolunteerAppeal', 'create', appeal).then( + function(success) { + return success.values.id; + }, + function(fail) { + var text = ts('Your submission was not saved. Resubmitting the form is unlikely to resolve this problem. Please contact a system administrator.'); + var title = ts('A technical problem has occurred'); + crmUiAlert({text: text, title: title, type: 'error'}); + } + ); + if (appealId) { + crmUiAlert({text: ts('Appeal Updated successfully'), title: ts('Updated'), type: 'success'}); + } + }; + + /* + * This function is used for delete appeal. + * Pass appeal Id in function. + * Using delete API for delete appeal. + * Remove that element from table. + **/ + $scope.deleteAppeal = function (id) { + CRM.confirm({message: ts("Are you sure you want to Delete the Appeal?")}).on('crmConfirm:yes', function() { + crmApi("VolunteerAppeal", "delete", {id: id}, true).then(function() { + let appeals_details = $scope.appeals; + // Remove that element from table based on id without page load. + $scope.appeals = appeals_details.filter(function( obj ) { + return obj.id !== id; + }); + }); + }); + }; + }); + // https://github.com/uxitten/polyfill/blob/master/string.polyfill.js + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart + if (!String.prototype.padStart) { + String.prototype.padStart = function padStart(targetLength,padString) { + targetLength = targetLength>>0; //truncate if number or convert non-number to 0; + padString = String((typeof padString !== 'undefined' ? padString : ' ')); + if (this.length > targetLength) { + return String(this); + } + else { + targetLength = targetLength-this.length; + if (targetLength > padString.length) { + padString += padString.repeat(targetLength/padString.length); //append to original to ensure we are longer than needed + } + return padString.slice(0,targetLength) + String(this); + } + }; + } + if (!String.prototype.repeat) { + String.prototype.repeat = function(count) { + 'use strict'; + if (this == null) { + throw new TypeError('can\'t convert ' + this + ' to object'); + } + var str = '' + this; + count = +count; + if (count != count) { + count = 0; + } + if (count < 0) { + throw new RangeError('repeat count must be non-negative'); + } + if (count == Infinity) { + throw new RangeError('repeat count must be less than infinity'); + } + count = Math.floor(count); + if (str.length == 0 || count == 0) { + return ''; + } + // Ensuring count is a 31-bit integer allows us to heavily optimize the + // main part. But anyway, most current (August 2014) browsers can't handle + // strings 1 << 28 chars or longer, so: + if (str.length * count >= 1 << 28) { + throw new RangeError('repeat count must not overflow maximum string size'); + } + var maxCount = str.length * count; + count = Math.floor(Math.log(count) / Math.log(2)); + while (count) { + str += str; + count--; + } + str += str.substring(0, maxCount - str.length); + return str; + } + } + +})(angular, CRM.$, CRM._); \ No newline at end of file diff --git a/ang/volunteer/LogHours.html b/ang/volunteer/LogHours.html new file mode 100644 index 00000000..4c8c6f29 --- /dev/null +++ b/ang/volunteer/LogHours.html @@ -0,0 +1,106 @@ + + +
+
+
+
+
+
+ +
+
+
+
+
+

+ {{ts('Log the time you volunteered. First, please select the organization you volunteered for.')}} +

+
+
+
+ +
+
+
+ +
+
+

+ {{ts('Click a project summary to see the full preview, then click "Next" to select it.')}} +

+
+ +
+ +
+ + + + + +
+ +
+
+

+ {{ts("You've registered for the following \"%1\" volunteer opportunities. Please log your time for them below. If you haven't performed these hours yet or wish to log them later, click the \"Remove\" button to remove it from the list.", {1: projects[wizardSelections.projectId].title})}} +

+
+ + +
+

+ {{ts('To log additional time to this project, add more rows below.')}} +

+

+ {{ts('Log your time for the \"%1\" project below. Add as many rows as needed.', {1: projects[wizardSelections.projectId].title})}} +

+
+ + + + + + +
+
+
+
+
+

Volunteer Time Logged

+

Thank you.

+ + + +
+
diff --git a/ang/volunteer/LogHours.js b/ang/volunteer/LogHours.js new file mode 100644 index 00000000..804f87b9 --- /dev/null +++ b/ang/volunteer/LogHours.js @@ -0,0 +1,158 @@ +(function(angular, $, _) { + + angular.module('volunteer').config(function($routeProvider) { + $routeProvider.when('/volunteer/log', { + controller: 'VolunteerLogHours', + resolve: { + supportingData: function(crmApi) { + return crmApi('VolunteerUtil', 'getsupportingdata', { + controller: 'VolunteerLogHours' + }).then(function(success) { + return success.values; + }); + } + }, + templateUrl: '~/volunteer/LogHours.html' + }); + } + ); + + angular.module('volunteer').controller('VolunteerLogHours', function($scope, $window, $location, $route, crmApi, crmUiAlert, crmUiHelp, supportingData) { + // The ts() and hs() functions help load strings for this module. + var ts = $scope.ts = CRM.ts('org.civicrm.volunteer'); + var hs = $scope.hs = crmUiHelp({file: 'CRM/Volunteer/Form/Volunteer'}); // See: templates/CRM/volunteer/Project.hlp + + // Having two collections for the same model is somewhat dirty, but there's no + // (worthwhile) way to filter the list and preserve two-way binding in the view. + var newTimeEntries = []; + var existingTimeEntries = []; + $scope.newTimeEntries = newTimeEntries; + $scope.existingTimeEntries = existingTimeEntries; + + var projects = {}; + + // Hardcoded for now. Located in the controller for future extensibility + // (e.g., set via retrieved setting). + $scope.locBlockHeading = ts('Location:'); + $scope.projects = projects; + $scope.supportingData = supportingData; + $scope.wizardSelections = {}; + $scope.submitted = false; // TODO: can probably use a framework provided variable instead. + + $scope.addNewTimeEntry = function() { + if ($scope.wizardSelections.projectId) { + var flexibleNeedId = $scope.projects[$scope.wizardSelections.projectId]['api.VolunteerNeed.getvalue']; + $scope.newTimeEntries.push({ + assignee_contact_id: CRM.vars['org.civicrm.volunteer'].currentContactId, + volunteer_need_id: flexibleNeedId + }); + } + }; + + $scope.goHome = function() { + $window.location.href = '/'; + }; + + $scope.goTo = function(url) { + $location.path(url); + } + + $scope.saveTimeEntries = function() { + $scope.submitted = true; + CRM.$('div[ng-form=enterDetailsForm]').block(); + var requests = _.map($scope.newTimeEntries.concat($scope.existingTimeEntries), function(paramsObj) { + paramsObj.status_id = _.invert(supportingData.volunteer_status)['Completed']; + return ['VolunteerAssignment', 'create', paramsObj]; + }); + crmApi(requests).then(function(success){ + CRM.$('div[ng-form=enterDetailsForm]').unblock(); + }, function(fail){ + // Do something with the failure... I suspect cases where the Internet + // connection fails land here... not sure what else. Since we are making + // multiple requests through one API connection, the result is a little + // different, and we should expect to handle application failures (e.g., + // "required field X missing") in the success handler. + }); + }; + + $scope.selectProject = function(id) { + $scope.wizardSelections.projectId = id; + }; + + $scope.startOver = function() { + $route.reload(); + } + + // Refresh the project list when the beneficiary is changed. + $scope.$watch('wizardSelections.beneficiaryId', function (newValue, oldValue, scope) { + if (newValue) { + crmApi('VolunteerProject', 'get', { + project_contacts: {volunteer_beneficiary: newValue}, + 'api.VolunteerProject.getlocblockdata': { + id: '$value.loc_block_id', + options: {limit: 0}, + return: 'all', + sequential: 1 + }, + 'api.VolunteerNeed.getvalue': { + is_active: 1, + is_flexible: 1, + return: 'id' + } + }).then(function(success) { + // format the location data for easier use + var values = success.values; + _.each(values, function(value, key) { + var loc_block = value['api.VolunteerProject.getlocblockdata']['count'] ? value['api.VolunteerProject.getlocblockdata']['values'][0] : {}; + values[key]['loc_block'] = loc_block; + delete values[key]['api.VolunteerProject.getlocblockdata']; + }); + $scope.projects = values; + }, function (fail){ + // do something with the failure, eh? + }); + } + }); + + // Refresh the list of time entries when the project is changed. + $scope.$watch('wizardSelections.projectId', function (newValue, oldValue, scope) { + // reset the list of time entries + $scope.existingTimeEntries = []; + // always start with an empty row + $scope.newTimeEntries = []; + $scope.addNewTimeEntry(); + + if (newValue) { + // TODO: This has the potential to be pretty inefficient. We're pulling + // a list of all activities assigned to the contact and then filtering + // them via the chained API. It would be better to modify + // api.VolunteerAssignment.get to take parameters related to table + // civicrm_activity_contact so that we can filter on assignee. + crmApi('ActivityContact', 'get', { + contact_id: "user_contact_id", + record_type_id: "Activity Assignees", + sequential: 1, + "api.VolunteerAssignment.get": { + id: "$value.activity_id", + project_id: newValue, + sequential: 1 + } + }).then(function(success) { + _.each(success.values, function(value, key) { + if (value['api.VolunteerAssignment.get'].count) { + // Account for inconsistent property names: role_id in get; volunteer_role_id in create. + _.each(value['api.VolunteerAssignment.get'].values, function(v, k) { + v.volunteer_role_id = v.role_id; + }); + Array.prototype.push.apply($scope.existingTimeEntries, value['api.VolunteerAssignment.get'].values); + } + }); + }, function (fail){ + // do something with the failure, eh? + }); + } + }); + + }); + +})(angular, CRM.$, CRM._); diff --git a/ang/volunteer/Project.html b/ang/volunteer/Project.html index ffefa74b..2284b63f 100644 --- a/ang/volunteer/Project.html +++ b/ang/volunteer/Project.html @@ -30,14 +30,14 @@ - +
+ +
+
+
{{totalRec}} {{ ts('Opportunities') }}
+
+ +
+
+ +
+
+
+ + + + +
+
+ + + + +
+
+ + + +
+
+ + + +
+
+ +
+
+ {{ts('No results found. Please try different search criteria.')}} +
+
+
+
+ + {{currentPage}}/{{numberOfPages}} + +
+
\ No newline at end of file diff --git a/ang/volunteer/VolApplsCtrl.js b/ang/volunteer/VolApplsCtrl.js new file mode 100644 index 00000000..3c2a72f6 --- /dev/null +++ b/ang/volunteer/VolApplsCtrl.js @@ -0,0 +1,379 @@ +(function (angular, $, _) { + + angular.module('volunteer').config(function ($routeProvider) { + $routeProvider.when('/volunteer/appeals', { + controller: 'VolApplsCtrl', + // update the search params in the URL without reloading the route + templateUrl: '~/volunteer/VolApplsCtrl.html', + resolve: { + custom_fieldset_volunteer: function(crmApi) { + return crmApi('VolunteerAppeal', 'getCustomFieldsetWithMetaVolunteerAppeal', { + controller: 'VolunteerAppeal' + }); + }, + supporting_data: function(crmApi, $route) { + return crmApi('VolunteerUtil', 'getsupportingdata', { + controller: 'VolunteerAppealDetail', + appeal_id: $route.current.params.appealId + }); + }, + } + }); + }); + + angular.module('volunteer').controller('VolApplsCtrl', function ($route, $scope, crmApi, $window, custom_fieldset_volunteer, supporting_data, $location) { + + if (!$window.location.origin) { + $window.location.origin = $window.location.protocol + "//" + + $window.location.hostname + + ($window.location.port ? ':' + $window.location.port : ''); + } + + var ts = $scope.ts = CRM.ts('org.civicrm.volunteer'); + + $scope.search=""; + $scope.currentTemplate = "~/volunteer/AppealGrid.html"; //default view is grid view + $scope.totalRec; + $scope.currentPage = 1; + $scope.pageSize = 10; + $scope.appealCustomFieldData = {}; + $scope.options = [{key:"dateE",val:"Upcoming"},{key:"dateS",val:ts('Newest Opportunities')},{key:"titleA",val:"Title A-Z"},{key:"titleD",val:"Title Z-A"},{key:"benfcrA",val:"Project Beneficiary A-Z"},{key:"benfcrD",val:"Project Beneficiary Z-A"}]; + $scope.sortValue = $scope.options[0]; + $scope.sortby=$scope.order = null; + $scope.activeGrid = "grid_view"; + $scope.beneficiary_name = []; + $scope.custom_field_display = []; + $scope.supporting_data = supporting_data.values; + + //Change reult view + $scope.changeview = function(tpl, type){ + $scope.activeGrid = type; + $scope.currentTemplate = tpl; + } + // Clear checkbox selection in Date and Location Filter. + $scope.clear = function clear() { + $scope.location_finder_way = null; + $scope.lat = null; + $scope.lon = null; + $scope.postal_code = null; + }; + // Assign custom field set values. + $scope.custom_fieldset_volunteer = custom_fieldset_volunteer.values; + + //Get appeal data with search text and/or pagination + getAppeals = function (advancedFilter,filterObj, firstTime) { + CRM.$('#crm-main-content-wrapper').block(); + // this line will check if the argument is undefined, null, or false + // if so set it to false, otherwise set it to it's original value + var firstTime = firstTime || false; + let params={}; + if($window.localStorage.getItem("search_params") && firstTime == true) { + params = JSON.parse($window.localStorage.getItem("search_params")); + params.page_no ? $scope.currentPage=params.page_no : null; + params.search_appeal ? $scope.search=params.search_appeal : null; + params.orderby ? $scope.sortby=params.orderby : null; + params.order ? $scope.order=params.order : null; + params.sortOption ? $scope.sortValue=$scope.options[params.sortOption] : 0; + params.advanced_search_option ? $scope.advanced_search=params.advanced_search_option : false; + if(params.advanced_search_option) { + params.advanced_search.fromdate ? $scope.date_start=params.advanced_search.fromdate : null; + params.advanced_search.todate ? $scope.date_end=params.advanced_search.todate : null; + + if(params.advanced_search.show_appeals_done_anywhere) { + params.advanced_search.show_appeals_done_anywhere ? $scope.show_appeals_done_anywhere=params.advanced_search.show_appeals_done_anywhere : null; + } else { + if(params.advanced_search.proximity) { + params.advanced_search.proximity.radius ? $scope.radius=params.advanced_search.proximity.radius : null; + params.advanced_search.proximity.unit ? $scope.unit=params.advanced_search.proximity.unit : null; + } + params.location_finder_way ? $scope.location_finder_way=params.location_finder_way : null; + if(params.location_finder_way == "use_postal_code") { + params.advanced_search.proximity.postal_code ? $scope.postal_code=params.advanced_search.proximity.postal_code : null; + } + if(params.location_finder_way == "use_my_location") { + params.advanced_search.proximity.lat ? $scope.lat=params.advanced_search.proximity.lat : null; + params.advanced_search.proximity.lon ? $scope.lon=params.advanced_search.proximity.lon : null; + } + } + params.advanced_search.appealCustomFieldData ? $scope.appealCustomFieldData=params.advanced_search.appealCustomFieldData : null; + } + } + $scope.currentPage?params.page_no=$scope.currentPage:null; + $scope.search?params.search_appeal=$scope.search:null; + $scope.sortby?params.orderby=$scope.sortby:null; + $scope.order?params.order=$scope.order:null; + if($scope.advanced_search) { + // Default Proximity Object Set to empty. + params.advanced_search={proximity:{}}; + $scope.date_start?params.advanced_search.fromdate=$scope.date_start:null; + $scope.date_end?params.advanced_search.todate=$scope.date_end:null; + // If Show appeals done anywhere checkbox is disable then and then proximity set. + if(!$scope.show_appeals_done_anywhere) { + $scope.radius?params.advanced_search.proximity.radius=$scope.radius:null; + $scope.unit?params.advanced_search.proximity.unit=$scope.unit:null; + if($scope.location_finder_way == "use_postal_code") { + $scope.postal_code?params.advanced_search.proximity.postal_code=$scope.postal_code:null; + } else { + $scope.lat?params.advanced_search.proximity.lat=$scope.lat:null; + $scope.lon?params.advanced_search.proximity.lon=$scope.lon:null; + } + } + $scope.show_appeals_done_anywhere?params.advanced_search.show_appeals_done_anywhere=$scope.show_appeals_done_anywhere:null; + // Pass custom field data from advance search to API. + params.advanced_search.appealCustomFieldData = $scope.appealCustomFieldData; + } + var current_parms = $route.current.params; + if (current_parms.beneficiary && typeof current_parms.beneficiary === "string") { + params.beneficiary = current_parms.beneficiary; + } + if(params.beneficiary) { + var beneficiaryArray = params.beneficiary.split(","); + for(var i = 0; i < beneficiaryArray.length; i++) { + CRM.api3('Contact', 'get', { + "sequential": 1, + "id": beneficiaryArray[i] + }).then(function(result) { + if(result && result.values.length > 0) { + if(!!($scope.beneficiary_name.indexOf(result.values[0].display_name)+1) == false) { + $scope.beneficiary_name.push(result.values[0].display_name); + } + } + }, function(error) { + // oops + console.log(error); + }); + } + } + if(params.advanced_search) { + for (var key in params.advanced_search.appealCustomFieldData) { + if (params.advanced_search.appealCustomFieldData.hasOwnProperty(key)) { + var customFieldArray = key.split("_"); + CRM.api3('CustomField', 'get', { + "sequential": 1, + "id": customFieldArray[1] + }).then(function(result) { + var group_id = result.values[0].custom_group_id; + CRM.api3('CustomGroup', 'get', { + "sequential": 1, + "id": group_id + }).then(function(result) { + if(!!($scope.custom_field_display.indexOf(result.values[0].title)+1) == false) { + $scope.custom_field_display.push(result.values[0].title); + } + }, function(error) { + // oops + }); + }, function(error) { + // oops + }); + } + } + } + return crmApi('VolunteerAppeal', 'getsearchresult', params) + .then(function (data) { + let projectAppeals=[]; + for(let key in data.values.appeal) { + projectAppeals.push(data.values.appeal[key]); + } + $scope.appeals=projectAppeals; + $scope.totalRec=data.values.total_appeal; + $scope.numberOfPages= Math.ceil($scope.totalRec/$scope.pageSize); + $scope.closeModal(); + CRM.$('#crm-main-content-wrapper').unblock(); + + var sortOption = $scope.options.findIndex(function(option) { + return option.key == $scope.sortValue.key; + }); + params.sortOptionKey = $scope.sortValue.key; + params.sortOption = sortOption; + params.location_finder_way = $scope.location_finder_way; + params.advanced_search_option = $scope.advanced_search; + $window.localStorage.setItem("search_params", JSON.stringify(params)); + $scope.active_search = params; + },function(error) { + CRM.$('#crm-main-content-wrapper').unblock(); + if (error.is_error) { + CRM.alert(error.error_message, ts("Error"), "error"); + } else { + return error; + } + }); + } + //Loading list on first time + getAppeals('','',true); + + //update current page to previouse and get result data + $scope.prevPageData=function(){ + $scope.currentPage=$scope.currentPage-1; + getAppeals(); + } + + //update current page to next and get result data + $scope.nextPageData=function(){ + $scope.currentPage=$scope.currentPage+1; + getAppeals(); + } + + //reset page count and search data + $scope.searchRes=function(){ + $scope.currentPage = 1; + getAppeals(); + } + + //sort title,dates,beneficiaries by asc and desc + $scope.sort=function(){ + $scope.currentPage = 1; + checkAndSetSortValue(); + getAppeals(); + } + + //Set sort by and order by according to value selected + function checkAndSetSortValue() { + let sortby,orderby=null; + if($scope.sortValue.key=="titleA"){ + sortby="title"; + orderby="ASC"; + } else if ($scope.sortValue.key=="titleD"){ + sortby="title"; + orderby="DESC"; + } else if($scope.sortValue.key=="dateS"){ + sortby="active_fromdate"; + orderby="DESC"; + } else if($scope.sortValue.key=="dateE"){ + sortby="upcoming_appeal"; + orderby="DESC"; + } else if($scope.sortValue.key=="benfcrA"){ + sortby="project_beneficiary"; + orderby="ASC"; + } else if($scope.sortValue.key=="benfcrD"){ + sortby="project_beneficiary"; + orderby="DESC"; + } + $scope.sortby=sortby; + $scope.order=orderby; + } + + $scope.redirectTo=function(appealId) { + $location.path("/volunteer/appeal/"+appealId); + } + + $scope.volSignup= function(need_flexi_id,projectId,hide_appeal_volunteer_button) { + if(hide_appeal_volunteer_button == "1") { + // TODO + $window.location.href = CRM.url("civicrm/vol/#/volunteer/opportunities", "project="+projectId+"&hideSearch=1"); + } else { + $window.location.href =CRM.url("civicrm/volunteer/signup", "reset=1&needs[]="+need_flexi_id+"&dest=list"); + } + } + + $scope.openModal=function(){ + $scope.modalClass="modalDialog" + } + + $scope.closeModal=function() { + $scope.modalClass=null; + } + + $scope.active = 1; + $scope.selectTab = function(value){ + $scope.active = value; + } + + $scope.isActive = function(value){ + if($scope.active==value){ + return true; + } + else { + return false; + } + } + + $scope.getPosition = function (){ + if(navigator.geolocation){ + navigator.geolocation.getCurrentPosition(showPosition); + } else { + alert("Sorry, your browser does not support HTML5 geolocation."); + } + } + + function showPosition(position) { + $scope.$apply(function() { + $scope.lat = position.coords.latitude; + $scope.lon= position.coords.longitude; + }); + }; + + $scope.proximityUnits = [ + {value: 'km', label: ts('km')}, + {value: 'miles', label: ts('miles')} + ]; + + $scope.radiusvalue = [ + {value: 2, label: ts('2')}, + {value: 5, label: ts('5')}, + {value: 10, label: ts('10')}, + {value: 25, label: ts('25')}, + {value: 100, label: ts('100')} + ]; + + $scope.advanceFilter=function() { + let params={proximity:{}}; + $scope.advanced_search = true; + $scope.currentPage = 1; + getAppeals(); + } + + $scope.resetFilter=function() { + $window.localStorage.removeItem("search_params"); + $location.search('beneficiary', null); + $route.reload(); + } + + // https://tc39.github.io/ecma262/#sec-array.prototype.findIndex + if (!Array.prototype.findIndex) { + Object.defineProperty(Array.prototype, 'findIndex', { + value: function(predicate) { + // 1. Let O be ? ToObject(this value). + if (this == null) { + throw new TypeError('"this" is null or not defined'); + } + + var o = Object(this); + + // 2. Let len be ? ToLength(? Get(O, "length")). + var len = o.length >>> 0; + + // 3. If IsCallable(predicate) is false, throw a TypeError exception. + if (typeof predicate !== 'function') { + throw new TypeError('predicate must be a function'); + } + + // 4. If thisArg was supplied, let T be thisArg; else let T be undefined. + var thisArg = arguments[1]; + + // 5. Let k be 0. + var k = 0; + + // 6. Repeat, while k < len + while (k < len) { + // a. Let Pk be ! ToString(k). + // b. Let kValue be ? Get(O, Pk). + // c. Let testResult be ToBoolean(? Call(predicate, T, « kValue, k, O »)). + // d. If testResult is true, return k. + var kValue = o[k]; + if (predicate.call(thisArg, kValue, k, o)) { + return k; + } + // e. Increase k by 1. + k++; + } + + // 7. Return -1. + return -1; + } + }); + } + + }); + +})(angular, CRM.$, CRM._); \ No newline at end of file diff --git a/ang/volunteer/VolOppsCtrl.html b/ang/volunteer/VolOppsCtrl.html index c1c7d980..e84797c1 100644 --- a/ang/volunteer/VolOppsCtrl.html +++ b/ang/volunteer/VolOppsCtrl.html @@ -22,7 +22,7 @@