From: Pascal Corpet Date: Sun, 25 Jul 2010 19:26:59 +0000 (+0200) Subject: Replace sectors by job terms in profile and search (job and mentoring). X-Git-Tag: xorg/1.0.1~247 X-Git-Url: http://git.polytechnique.org/?a=commitdiff_plain;h=3ac45f10c6b54e4db4bfeb0aeb5eef0f5ee5d1b2;p=platal.git Replace sectors by job terms in profile and search (job and mentoring). --- diff --git a/classes/direnum.php b/classes/direnum.php index 8415ee3..1382019 100644 --- a/classes/direnum.php +++ b/classes/direnum.php @@ -51,6 +51,7 @@ class DirEnum const COMPANIES = 'companies'; const SECTORS = 'sectors'; const JOBDESCRIPTION = 'jobdescription'; + const JOBTERMS = 'jobterms'; const NETWORKS = 'networking'; @@ -646,6 +647,30 @@ class DE_JobDescription extends DirEnumeration } // }}} +// {{{ class DE_JobTerms +class DE_JobTerms extends DirEnumeration +{ + // {{{ function getAutoComplete + public function getAutoComplete($text) + { + $tokens = JobTerms::tokenize($text.'%'); + if (count($tokens) == 0) { + return PlIteratorUtils::fromArray(array()); + } + $token_join = JobTerms::token_join_query($tokens, 'e'); + return XDB::iterator('SELECT e.jtid AS id, e.full_name AS field, COUNT(DISTINCT p.pid) AS nb + FROM profile_job_term_enum AS e + INNER JOIN profile_job_term_relation AS r ON (r.jtid_1 = e.jtid) + INNER JOIN profile_job_term AS p ON (r.jtid_2 = p.jtid) + '.$token_join.' + GROUP BY e.jtid + ORDER BY nb DESC, field + LIMIT ' . self::AUTOCOMPLETE_LIMIT); + } + // }}} +} +// }}} + /** NETWORKING */ // {{{ class DE_Networking diff --git a/classes/jobterms.php b/classes/jobterms.php new file mode 100644 index 0000000..460b74e --- /dev/null +++ b/classes/jobterms.php @@ -0,0 +1,144 @@ +changeTpl('include/jobterms.branch.tpl', NO_SKIN); + $subTerms = self::getSubTerms(Env::v('jtid'), $filter); + $page->assign('subTerms', $subTerms); + switch ($filter) { + case self::ONLY_JOBS: + $page->assign('filter', 'camarade'); + break; + case self::ONLY_MENTORS: + $page->assign('filter', 'mentor'); + break; + } + $page->assign('attrfunc', Env::v('attrfunc')); + $page->assign('treeid', Env::v('treeid')); + } + + static public function jsAddTree($platalpage, $domElement = '.term_tree', $treeid = '', $attrfunc = '') { + return '$("'.addslashes($domElement).'").jstree({ + "core" : {"strings":{"loading":"Chargement ..."}}, + "plugins" : ["themes","json_data"], + "themes" : { "url" : platal_baseurl + "css/jstree.css" }, + "json_data" : { "ajax" : { + "url" : platal_baseurl + "'.addslashes($platalpage).'", + "data" : function(nod) { + var jtid = 0; + if (nod != -1) { + jtid = nod.attr("id").replace(/^.*_([0-9]+)$/, "$1"); + } + return { "jtid" : jtid, "treeid" : "'.addslashes($treeid).'", "attrfunc" : "'.addslashes($attrfunc).'" } + } + }} });'; + } + + /** + * Extract search token from term + * @param $term a utf-8 string that can contain any char + * @param an array of elementary tokens + */ + static public function tokenize($term) + { + $term = mb_strtoupper(replace_accent($term)); + $term = str_replace(array('/', ',', '(', ')', '"', '&', '»', '«'), ' ', $term); + $tokens = explode(' ', $term); + static $not_tokens = array('ET','AND','DE','DES','DU','D\'','OU','L\'','LA','LE','LES','PAR','AU','AUX','EN','SUR','UN','UNE','IN'); + foreach ($tokens as &$t) { + $t = preg_replace(array('/^-+/','/-+$/'), '', $t); + if (substr($t, 1, 1) == '\'' && in_array(substr($t, 0, 2), $not_tokens)) { + $t = substr($t, 2); + } + if (strlen($t) == 1 || in_array($t, $not_tokens)) { + $t = false; + continue; + } + } + return array_filter($tokens); + } + + /** + * Create the INNER JOIN query to restrict search to some job terms + * @param $tokens an array of the job terms to look for (LIKE comp) + * @param $table_alias the alias or name of the table with a jtid field to restrict + * @return a partial SQL query + */ + static public function token_join_query(array $tokens, $table_alias) { + $joins = ''; + $i = 0; + foreach ($tokens as $t) { + ++$i; + $joins .= ' INNER JOIN profile_job_term_search AS s'.$i.' ON(s'.$i.'.jtid = '.$table_alias.'.jtid AND s'.$i.'.search LIKE '.XDB::escape($t).')'; + } + return $joins; + } +} + +// vim:set et sw=4 sts=4 sws=4 foldmethod=marker enc=utf-8: +?> diff --git a/classes/profile.php b/classes/profile.php index 10d625b..a747063 100644 --- a/classes/profile.php +++ b/classes/profile.php @@ -155,10 +155,12 @@ class Profile const FETCH_MENTOR_SECTOR = 0x000040; const FETCH_MENTOR_COUNTRY = 0x000080; const FETCH_PHONES = 0x000100; + const FETCH_JOB_TERMS = 0x000200; + const FETCH_MENTOR_TERMS = 0x000400; const FETCH_MINIFICHES = 0x00012D; // FETCH_ADDRESSES | FETCH_EDU | FETCH_JOBS | FETCH_NETWORKING | FETCH_PHONES - const FETCH_ALL = 0x0001FF; // OR of FETCH_* + const FETCH_ALL = 0x0007FF; // OR of FETCH_* private $fetched_fields = 0x000000; @@ -481,8 +483,8 @@ class Profile private function fetched($field) { - if (!array_key_exists($field, ProfileField::$fields)) { - Platal::page()->kill("Invalid field: $field"); + if (($fields | self::FETCH_ALL) != self::FETCH_ALL) { + Platal::page()->kill("Invalid fetched fields: $fields"); } return ($this->fetched_fields & $field); @@ -494,6 +496,9 @@ class Profile */ private function getProfileField($field) { + if (!array_key_exists($field, ProfileField::$fields)) { + Platal::page()->kill("Invalid field: $field"); + } if ($this->fetched($field)) { return null; } else { @@ -522,6 +527,9 @@ class Profile if ($this->addresses != null && $this->jobs != null) { $this->jobs->addAddresses($this->addresses); } + if ($this->jobs != null && $this->jobterms != null) { + $this->jobs->addJobTerms($this->jobterms); + } } /* Photo @@ -732,6 +740,15 @@ class Profile return array_pop($job); } + /** JobTerms + */ + private $jobterms = null; + public function setJobTerms(ProfileJobTerms $jobterms) + { + $this->jobterms = $jobterms; + $this->consolidateFields(); + } + /* Mentoring */ private $mentor_sectors = null; @@ -772,6 +789,34 @@ class Profile } } + /** List of job terms to specify mentoring */ + private $mentor_terms = null; + /** + * set job terms to specify mentoring + * @param $terms a ProfileMentoringTerms object listing terms only for this profile + */ + public function setMentoringTerms(ProfileMentoringTerms $terms) + { + $this->mentor_terms = $terms; + } + /** + * get all job terms that specify mentoring + * @return an array of JobTerms objects + */ + public function getMentoringTerms() + { + if ($this->mentor_terms == null && !$this->fetched(self::FETCH_MENTOR_TERMS)) { + $this->setMentoringTerms($this->getProfileField(self::FETCH_MENTOR_TERMS)); + } + + if ($this->mentor_terms == null) { + return array(); + } else { + return $this->mentor_terms->get(); + } + } + + /* Binets */ public function getBinets() @@ -1124,6 +1169,8 @@ class ProfileIterator implements PlIterator private $fields; private $visibility; + const FETCH_ALL = 0x000033F; // FETCH_ADDRESSES | FETCH_CORPS | FETCH_EDU | FETCH_JOBS | FETCH_MEDALS | FETCH_NETWORKING | FETCH_PHONES | FETCH_JOB_TERMS + public function __construct(PlIterator $it, array $pids, $fields = 0x0000, ProfileVisibility $visibility = null) { require_once 'profilefields.inc.php'; @@ -1142,39 +1189,12 @@ class ProfileIterator implements PlIterator $callbacks[0] = PlIteratorUtils::arrayValueCallback('pid'); $cb = PlIteratorUtils::objectPropertyCallback('pid'); - if ($fields & Profile::FETCH_ADDRESSES) { - $callbacks[Profile::FETCH_ADDRESSES] = $cb; - $subits[Profile::FETCH_ADDRESSES] = new ProfileFieldIterator('ProfileAddresses', $pids, $visibility); - } - - if ($fields & Profile::FETCH_CORPS) { - $callbacks[Profile::FETCH_CORPS] = $cb; - $subits[Profile::FETCH_CORPS] = new ProfileFieldIterator('ProfileCorps', $pids, $visibility); - } - - if ($fields & Profile::FETCH_EDU) { - $callbacks[Profile::FETCH_EDU] = $cb; - $subits[Profile::FETCH_EDU] = new ProfileFieldIterator('ProfileEducation', $pids, $visibility); - } - - if ($fields & Profile::FETCH_JOBS) { - $callbacks[Profile::FETCH_JOBS] = $cb; - $subits[Profile::FETCH_JOBS] = new ProfileFieldIterator('ProfileJobs', $pids, $visibility); - } - - if ($fields & Profile::FETCH_MEDALS) { - $callbacks[Profile::FETCH_MEDALS] = $cb; - $subits[Profile::FETCH_MEDALS] = new ProfileFieldIterator('ProfileMedals', $pids, $visibility); - } - - if ($fields & Profile::FETCH_NETWORKING) { - $callbacks[Profile::FETCH_NETWORKING] = $cb; - $subits[Profile::FETCH_NETWORKING] = new ProfileFieldIterator('ProfileNetworking', $pids, $visibility); - } - - if ($fields & Profile::FETCH_PHONES) { - $callbacks[Profile::FETCH_PHONES] = $cb; - $subits[Profile::FETCH_PHONES] = new ProfileFieldIterator('ProfilePhones', $pids, $visibility); + $fields = $fields & self::FETCH_ALL; + for ($field = 1; $field < $fields; $field *= 2) { + if (($fields & $field) ) { + $callbacks[$field] = $cb; + $subits[$field] = new ProfileFieldIterator($field, $pids, $visibility); + } } $this->iterator = PlIteratorUtils::parallelIterator($subits, $callbacks, 0); @@ -1200,6 +1220,9 @@ class ProfileIterator implements PlIterator if ($this->hasData(Profile::FETCH_JOBS, $vals)) { $pf->setJobs($vals[Profile::FETCH_JOBS]); } + if ($this->hasData(Profile::FETCH_JOB_TERMS, $vals)) { + $pf->setJobTerms($vals[Profile::FETCH_JOB_TERMS]); + } if ($this->hasData(Profile::FETCH_CORPS, $vals)) { $pf->setCorps($vals[Profile::FETCH_CORPS]); diff --git a/classes/userfilter.php b/classes/userfilter.php index 2a9d077..6e29146 100644 --- a/classes/userfilter.php +++ b/classes/userfilter.php @@ -980,6 +980,35 @@ class UFC_Job_Sectorization implements UserFilterCondition } // }}} +// {{{ class UFC_Job_Terms +/** Filters users based on the job terms they assigned to one of their + * jobs. + * @param $val The ID of the job term, or an array of such IDs + */ +class UFC_Job_Terms implements UserFilterCondition +{ + private $val; + + public function __construct($val) + { + if (!is_array($val)) { + $val = array($val); + } + $this->val = $val; + } + + public function buildCondition(PlFilter &$uf) + { + $sub = $uf->addJobTermsFilter(count($this->val)); + $conditions = array(); + foreach ($this->val as $i => $jtid) { + $conditions[] = $sub[$i] . ' = ' . XDB::escape($jtid); + } + return implode(' AND ', $conditions); + } +} +// }}} + // {{{ class UFC_Job_Description /** Filters users based on their job description * @param $description The text being searched for @@ -2473,6 +2502,7 @@ class UserFilter extends PlFilter * pjsse => profile_job_subsector_enum * pjssse => profile_job_subsubsector_enum * pja => profile_job_alternates + * pjt => profile_job_terms */ private $with_pj = false; private $with_pje = false; @@ -2480,6 +2510,7 @@ class UserFilter extends PlFilter private $with_pjsse = false; private $with_pjssse = false; private $with_pja = false; + private $with_pjt = 0; public function addJobFilter() { @@ -2513,6 +2544,22 @@ class UserFilter extends PlFilter } } + /** + * Adds a filter on job terms of profile. + * @param $nb the number of job terms to use + * @return an array of the fields to filter (one for each term). + * Code using this function should used returned field as is (contains table and field name). + */ + public function addJobTermsFilter($nb = 1) + { + $this->with_pjt = $nb; + $jobtermstable = array(); + for ($i = 1; $i <= $nb; ++$i) { + $jobtermstable[] = 'pjtr_'.$i.'.jtid_1'; + } + return $jobtermstable; + } + protected function jobJoins() { $joins = array(); @@ -2534,6 +2581,12 @@ class UserFilter extends PlFilter if ($this->with_pja) { $joins['pja'] = PlSqlJoin::left('profile_job_alternates', '$ME.subsubsectorid = pj.subsubsectorid'); } + if ($this->with_pjt > 0) { + for ($i = 1; $i <= $this->with_pjt; ++$i) { + $joins['pjt_'.$i] = PlSqlJoin::left('profile_job_term', '$ME.pid = $PID'); + $joins['pjtr_'.$i] = PlSqlJoin::left('profile_job_term_relation', '$ME.jtid_2 = pjt_'.$i.'.jtid'); + } + } return $joins; } diff --git a/htdocs/css/default.css b/htdocs/css/default.css index cd673cb..4ae8637 100644 --- a/htdocs/css/default.css +++ b/htdocs/css/default.css @@ -484,6 +484,13 @@ div.adresse strong { font-size: 90%; } +div.adresse ul { + margin-top: 0px; + margin-bottom: 0px; + list-style-type: none; + padding-left: 0px; +} + #fiche .medal_frame { float: left; width: 33%; diff --git a/htdocs/css/jstree.css b/htdocs/css/jstree.css new file mode 100644 index 0000000..65d3403 --- /dev/null +++ b/htdocs/css/jstree.css @@ -0,0 +1,71 @@ +/* + * jsTree default theme 1.0 + * Supported features: dots/no-dots, icons/no-icons, focused, loading + * Supported plugins: ui (hovered, clicked), checkbox, contextmenu, search + */ + +.jstree-default li, +.jstree-default ins { background-image:url("../images/jstree.png"); background-repeat:no-repeat; background-color:transparent; } +.jstree-default li { background-position:-90px 0; background-repeat:repeat-y; } +.jstree-default li.jstree-last { background:transparent; } +.jstree-default .jstree-open > ins { background-position:-72px 0; } +.jstree-default .jstree-closed > ins { background-position:-54px 0; } +.jstree-default .jstree-leaf > ins { background-position:-36px 0; } + +.jstree-default .jstree-hovered { background:#e7f4f9; border:1px solid #d8f0fa; padding:0 2px 0 1px; } +.jstree-default .jstree-clicked { background:#beebff; border:1px solid #99defd; padding:0 2px 0 1px; } +.jstree-default a .jstree-icon { background-position:-56px -19px; } +.jstree-default a.jstree-loading .jstree-icon { background:url("../images/wait.gif") center center no-repeat !important; } + +.jstree-default .jstree-no-dots li, +.jstree-default .jstree-no-dots .jstree-leaf > ins { background:transparent; } +.jstree-default .jstree-no-dots .jstree-open > ins { background-position:-18px 0; } +.jstree-default .jstree-no-dots .jstree-closed > ins { background-position:0 0; } + +.jstree-default .jstree-no-icons a .jstree-icon { display:none; } + +.jstree-default .jstree-search { font-style:italic; } + +.jstree-default .jstree-no-icons .jstree-checkbox { display:inline-block; } +.jstree-default .jstree-no-checkboxes .jstree-checkbox { display:none !important; } +.jstree-default .jstree-checked > a > .jstree-checkbox { background-position:-38px -19px; } +.jstree-default .jstree-unchecked > a > .jstree-checkbox { background-position:-2px -19px; } +.jstree-default .jstree-undetermined > a > .jstree-checkbox { background-position:-20px -19px; } +.jstree-default .jstree-checked > a > .jstree-checkbox:hover { background-position:-38px -37px; } +.jstree-default .jstree-unchecked > a > .jstree-checkbox:hover { background-position:-2px -37px; } +.jstree-default .jstree-undetermined > a > .jstree-checkbox:hover { background-position:-20px -37px; } + +#vakata-dragged.jstree-default ins { background:transparent !important; } +#vakata-dragged.jstree-default .jstree-ok { background:url("../images/jstree.png") -2px -53px no-repeat !important; } +#vakata-dragged.jstree-default .jstree-invalid { background:url("../images/jstree.png") -18px -53px no-repeat !important; } +#jstree-marker.jstree-default { background:url("../images/jstree.png") -41px -57px no-repeat !important; } + +.jstree-default a.jstree-search { color:aqua; } + +#vakata-contextmenu.jstree-default-context, +#vakata-contextmenu.jstree-default-context li ul { background:#f0f0f0; border:1px solid #979797; -moz-box-shadow: 1px 1px 2px #999; -webkit-box-shadow: 1px 1px 2px #999; box-shadow: 1px 1px 2px #999; } +#vakata-contextmenu.jstree-default-context li { } +#vakata-contextmenu.jstree-default-context a { color:black; } +#vakata-contextmenu.jstree-default-context a:hover, +#vakata-contextmenu.jstree-default-context .vakata-hover > a { padding:0 5px; background:#e8eff7; border:1px solid #aecff7; color:black; -moz-border-radius:2px; -webkit-border-radius:2px; border-radius:2px; } +#vakata-contextmenu.jstree-default-context li.jstree-contextmenu-disabled a, +#vakata-contextmenu.jstree-default-context li.jstree-contextmenu-disabled a:hover { color:silver; background:transparent; border:0; padding:1px 4px; } +#vakata-contextmenu.jstree-default-context li.vakata-separator { background:white; border-top:1px solid #e0e0e0; margin:0; } +#vakata-contextmenu.jstree-default-context li ul { margin-left:-4px; } + +/* IE6 BEGIN */ +.jstree-default li, +.jstree-default ins, +#vakata-dragged.jstree-default .jstree-invalid, +#vakata-dragged.jstree-default .jstree-ok, +#jstree-marker.jstree-default { _background-image:url("d.gif"); } +.jstree-default .jstree-open ins { _background-position:-72px 0; } +.jstree-default .jstree-closed ins { _background-position:-54px 0; } +.jstree-default .jstree-leaf ins { _background-position:-36px 0; } +.jstree-default a ins.jstree-icon { _background-position:-56px -19px; } +#vakata-contextmenu.jstree-default-context ins { _display:none; } +#vakata-contextmenu.jstree-default-context li { _zoom:1; } +.jstree-default .jstree-undetermined a .jstree-checkbox { _background-position:-20px -19px; } +.jstree-default .jstree-checked a .jstree-checkbox { _background-position:-38px -19px; } +.jstree-default .jstree-unchecked a .jstree-checkbox { _background-position:-2px -19px; } +/* IE6 END */ diff --git a/htdocs/images/jstree.png b/htdocs/images/jstree.png new file mode 100644 index 0000000..8540175 Binary files /dev/null and b/htdocs/images/jstree.png differ diff --git a/htdocs/javascript/jobtermstree.js b/htdocs/javascript/jobtermstree.js new file mode 100644 index 0000000..f3a42eb --- /dev/null +++ b/htdocs/javascript/jobtermstree.js @@ -0,0 +1,51 @@ +/*************************************************************************** + * Copyright (C) 2003-2010 Polytechnique.org * + * http://opensource.polytechnique.org/ * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., * + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + ***************************************************************************/ + +/** + * Creates a job terms tree. + * @param domElement the jQuery selector string that defines the DOM element + * which should contain the tree. + * @param platalpage the base page to query for branches + * @param treeid an id unique for the tree in this page that will be used in + * clickFunc + * @param clickFunc name of a javascript function that will be called when a + * term is clicked. The three params of this function will be : treeid, the + * id of the job term clicked, and the full name of the job term clicked. + */ +function createJobTermsTree(domElement, platalpage, treeid, clickFunc) +{ + $(domElement).jstree({ + "core" : {"strings":{"loading":"Chargement ..."}}, + "plugins" : ["themes","json_data"], + "themes" : { "url" : platal_baseurl + "css/jstree.css" }, + "json_data" : { "ajax" : { + "url" : platal_baseurl + platalpage, + "data" : function(nod) { + var jtid = 0; + if (nod != -1) { + jtid = nod.attr("id").replace(/^.*_([0-9]+)$/, "$1"); + } + return { "jtid" : jtid, "treeid" : treeid, "attrfunc" : clickFunc } + } + }} }); +} + +// vim:set et sw=4 sts=4 sws=4 foldmethod=marker enc=utf-8: + diff --git a/htdocs/javascript/profile.js b/htdocs/javascript/profile.js index cafaba9..6145692 100644 --- a/htdocs/javascript/profile.js +++ b/htdocs/javascript/profile.js @@ -566,6 +566,125 @@ function addEntreprise(id) $('.entreprise_' + id).toggle(); } +/** + * Adds a job term in job profile page + * @param jobid id of profile's job among his different jobs + * @param jtid id of job term to add + * @param full_name full text of job term + * @return false if the term already exist for this job, true otherwise + */ +function addJobTerm(jobid, jtid, full_name) +{ + var termid = 0; + var parentpath; + var formvarname; + if (jobid < 0) { + parentpath = ''; + jobid = ''; + formvarname = 'terms'; + } else { + parentpath = '#job_'+jobid+' '; + formvarname = 'jobs['+jobid+'][terms]'; + } + var lastJobTerm = $(parentpath + '.job_term:last'); + if (lastJobTerm.length != 0) { + termid = parseInt(lastJobTerm.children('input').attr('name').replace(/^(jobs\[[0-9]+\]\[terms\]|terms)\[([0-9]+)\]\[jtid\]/, '$2')) + 1; + if ($('#job'+jobid+'_term'+jtid).length > 0) { + return false; + } + } + var newdiv = '
'+ + ''+full_name+''+ + ''+ + 'retirer'+ + '
'; + if (lastJobTerm.length == 0) { + $(parentpath + '.job_terms').prepend(newdiv); + } else { + lastJobTerm.after(newdiv); + } + $('#job'+jobid+'_term'+jtid+' img').css('cursor','pointer').click(removeJobTerm); + return true; +} + +/** + * Remove a job term in job profile page. + * Must be called from a button in a div containing the term + */ +function removeJobTerm() +{ + $(this).parent().remove(); +} + +/** + * Prepare display for autocomplete suggestions in job terms + * @param row an array of (title of term, id of term) + * @return text to display + * If id is negative, it is because there are too much terms to + * be displayed. + */ +function displayJobTerm(row) +{ + if (row[1] < 0) { + return '... précise ta recherche ...'; + } + return row[0]; +} + +/** + * Function called when a job term has been selected from autocompletion + * in search + * @param li is the list item (
  • ) that has been clicked + * The context is the jsquery autocomplete object. + */ +function selectJobTerm(li) +{ + if (li.extra[0] < 0) { + return; + } + var jobid = this.extraParams.jobid; + addJobTerm(jobid,li.extra[0],$(li).text()); + var search_input; + if (jobid < 0) { + search_input = $('.term_search')[0]; + } else { + search_input = $('#job_'+jobid+' .term_search')[0]; + } + search_input.value = ''; + search_input.focus(); +} + +/** + * Function to show or hide a terms tree in job edition + * @param jobid is the id of the job currently edited + */ +function toggleJobTermsTree(jobid) +{ + var treepath; + if (jobid < 0) { + treepath = ''; + } else { + treepath = '#job_'+jobid+' '; + } + treepath += '.term_tree'; + if ($(treepath + ' ul').length > 0) { + $(treepath).empty().removeClass().addClass('term_tree'); + return; + } + createJobTermsTree(treepath, 'profile/ajax/tree/jobterms/all', 'job' + jobid, 'chooseJobTerm'); +} + +/** + * Function called when a job term is chosen from terms tree + * @param treeid is the full id of the tree (must look like job3) + * @param jtid is the id of the job term chosen + * @param fullname is the complete name (understandable without context) of the term + */ +function chooseJobTerm(treeid, jtid, fullname) +{ + addJobTerm(treeid.replace(/^job(.*)$/, '$1'), jtid, fullname); +} + // {{{1 Skills function addSkill(cat) diff --git a/include/profil.func.inc.php b/include/profil.func.inc.php deleted file mode 100644 index e8ecb59..0000000 --- a/include/profil.func.inc.php +++ /dev/null @@ -1,379 +0,0 @@ - $bvar) { - if (isset($a[$val])) { - if ($a[$val] == $bvar) - unset($c[$val]); - else { - switch ($val) { - case 'adr' : if (!($c['adr'] = diff_user_addresses($a[$val], $bvar, $view))) unset($c['adr']); break; - case 'adr_pro' : if (!($c['adr_pro'] = diff_user_pros($a[$val], $bvar, $view))) unset($c['adr_pro']); break; - case 'tels' : if (!($c['tels'] = diff_user_tels($a[$val], $bvar, $view))) unset($c['tels']); break; - } - } - } - } - // don't modify freetext if you don't have the right - if (isset($b['freetext_pub']) && !has_user_right($b['freetext_pub'], $view) && isset($c['freetext'])) - unset($c['freetext']); - if (!count($c)) - return false; - return $c; -} - -function same_tel(&$a, &$b) { - $numbera = format_phone_number((string) $a); - $numberb = format_phone_number((string) $b); - return $numbera === $numberb; -} -function same_address(&$a, &$b) { - return - (same_field($a['adr1'],$b['adr1'])) && - (same_field($a['adr2'],$b['adr2'])) && - (same_field($a['adr3'],$b['adr3'])) && - (same_field($a['postcode'],$b['postcode'])) && - (same_field($a['city'],$b['city'])) && - (same_field($a['countrytxt'],$b['countrytxt'])) && - true; -} -function same_pro(&$a, &$b) { - return - (same_field($a['entreprise'],$b['entreprise'])) && - (same_field($a['fonction'],$b['fonction'])) && - true; -} -function same_field(&$a, &$b) { - if ($a == $b) return true; - if (is_array($a)) { - if (!is_array($b) || count($a) != count($b)) return false; - foreach ($a as $val => $avar) - if (!isset($b[$val]) || !same_field($avar, $b[$val])) return false; - return true; - } elseif (is_string($a)) - return (mb_strtoupper($a) == mb_strtoupper($b)); -} -function diff_user_tel(&$a, &$b) { - $c = $a; - if (isset($b['tel_pub']) && isset($a['tel_pub']) && has_user_right($b['tel_pub'], $a['tel_pub'])) - $c['tel_pub'] = $b['tel_pub']; - foreach ($b as $val => $bvar) { - if (isset($a[$val])) { - if ($a[$val] == $bvar) - unset($c[$val]); - } - } - if (!count($c)) - return false; - $c['telid'] = $a['telid']; - return $c; -} - -function diff_user_tels(&$a, &$b) -{ - $c = $a; - $telids_b = array(); - foreach ($b as $i => $telb) $telids_b[$telb['telid']] = $i; - - foreach ($a as $j => $tela) { - if (isset($tela['telid'])) { - // if b has a tel with the same telid, compute diff - if (isset($telids_b[$tela['telid']])) { - if (!($c[$j] = diff_user_tel($tela, $b[$telids_b[$tela['adrid']]]))) { - unset($c[$j]); - } - unset($telids_b[$tela['telid']]); - } - } else { - // try to find a match in b - foreach ($b as $i => $telb) { - if (same_tel($tela['tel'], $telb['tel'])) { - $tela['telid'] = $telb['telid']; - if (!($c[$j] = diff_user_tel($tela, $telb))) { - unset($c[$j]); - } - unset($telids_b[$tela['telid']]); - break; - } - } - } - } - - foreach ($telids_b as $telidb => $i) - $c[] = array('telid' => $telidb, 'remove' => 1); - return $c; -} - -function diff_user_address($a, $b) { - if (isset($b['pub']) && isset($a['pub']) && has_user_right($b['pub'], $a['pub'])) - $a['pub'] = $b['pub']; - if (isset($b['tels'])) { - if (isset($a['tels'])) { - $avar = $a['tels']; - } else { - $avar = array(); - } - $ctels = diff_user_tels($avar, $b['tels']); - - if (!count($ctels)) { - $b['tels'] = $avar; - } else { - $a['tels'] = $ctels; - } - } - - foreach ($a as $val => $avar) { - if (!isset($b[$val]) || !same_field($avar,$b[$val])) { - return $a; - } - } - return false; -} - -// $b need to use adrids -function diff_user_addresses(&$a, &$b) { - $c = $a; - $adrids_b = array(); - foreach ($b as $i => $adrb) $adrids_b[$adrb['adrid']] = $i; - - foreach ($a as $j => $adra) { - if (isset($adra['adrid'])) { - // if b has an address with the same adrid, compute diff - if (isset($adrids_b[$adra['adrid']])) { - if (!($c[$j] = diff_user_address($adra, $b[$adrids_b[$adra['adrid']]]))) - unset($c[$j]); - unset($adrids_b[$adra['adrid']]); - } - } else { - // try to find a match in b - foreach ($b as $i => $adrb) { - if (same_address($adra, $adrb)) { - $adra['adrid'] = $adrb['adrid']; - if (!($c[$j] = diff_user_address($adra, $adrb))) - unset($c[$j]); - if ($c[$j]) $c[$j]['adrid'] = $adra['adrid']; - unset($adrids_b[$adra['adrid']]); - break; - } - } - } - } - - foreach ($adrids_b as $adridb => $i) - $c[] = array('adrid' => $adridb, 'remove' => 1); - - if (!count($c)) return false; - return $c; -} - -function diff_user_pro($a, &$b, $view = 'private') { - if (isset($b['pub']) && isset($a['pub']) && has_user_right($b['pub'], $a['pub'])) - $a['pub'] = $b['pub']; - if (isset($b['adr_pub']) && !has_user_right($b['adr_pub'], $view)) { - unset($a['adr1']); - unset($a['adr2']); - unset($a['adr3']); - unset($a['postcode']); - unset($a['city']); - unset($a['countrytxt']); - unset($a['region']); - } - if (isset($b['adr_pub']) && isset($a['adr_pub']) && has_user_right($b['adr_pub'], $a['adr_pub'])) - $a['adr_pub'] = $b['adr_pub']; - if (isset($b['tels'])) { - if (isset($a['tels'])) - $avar = $a['tels']; - else - $avar = array(); - $ctels = diff_user_tels($avar, $b['tels']); - - if (!count($ctels)) { - $b['tels'] = $avar; - } else - $a['tels'] = $ctels; - } - if (isset($b['email_pub']) && !has_user_right($b['email_pub'], $view)) - unset($a['email']); - if (isset($b['email_pub']) && isset($a['email_pub']) && has_user_right($b['email_pub'], $a['email_pub'])) - $a['email_pub'] = $b['email_pub']; - foreach ($a as $val => $avar) { - if (($avar && !isset($b[$val])) || !same_field($avar,$b[$val])) { - return $a; - } - } - return false; -} - -// $b need to use entrids -function diff_user_pros(&$a, &$b, $view = 'private') { - $c = $a; - $entrids_b = array(); - foreach ($b as $i => $prob) $entrids_b[$prob['entrid']] = $i; - - foreach ($a as $j => $proa) { - if (isset($proa['entrid'])) { - // if b has an address with the same adrid, compute diff - if (isset($entrids_b[$proa['entrid']])) { - if (!($c[$j] = diff_user_pro($proa, $b[$entrids_b[$proa['entrid']]], $view))) - unset($c[$j]); - unset($entrids_b[$proa['entrid']]); - } - } else { - // try to find a match in b - foreach ($b as $i => $prob) { - if (same_pro($proa, $prob)) { - $proa['entrid'] = $prob['entrid']; - if (!($c[$j] = diff_user_pro($proa, $prob, $view))) - unset($c[$j]); - if ($c[$j]) $c[$j]['entrid'] = $proa['entrid']; - unset($entrids_b[$proa['entrid']]); - break; - } - } - } - } - - foreach ($entrids_b as $entridb => $i) - $c[] = array('entrid' => $entridb, 'remove' => 1); - - if (!count($c)) return false; - return $c; -} - -function format_phone_number($tel) -{ - $tel = trim($tel); - if (substr($tel, 0, 3) === '(0)') { - $tel = '33' . $tel; - } - $tel = preg_replace('/\(0\)/', '', $tel); - $tel = preg_replace('/[^0-9]/', '', $tel); - if (substr($tel, 0, 2) === '00') { - $tel = substr($tel, 2); - } else if(substr($tel, 0, 1) === '0') { - $tel = '33' . substr($tel, 1); - } - return $tel; -} - -function format_display_number($tel, &$error, $format = array('format'=>'','phoneprf'=>'')) -{ - $error = false; - $ret = ''; - $tel_length = strlen($tel); - if((!isset($format['phoneprf'])) || ($format['phoneprf'] == '')) { - $res = XDB::query("SELECT phonePrefix AS phoneprf, phoneFormat AS format - FROM geoloc_countries - WHERE phonePrefix = {?} OR phonePrefix = {?} OR phonePrefix = {?} - LIMIT 1", - substr($tel, 0, 1), substr($tel, 0, 2), substr($tel, 0, 3)); - if ($res->numRows() == 0) { - $error = true; - return '+' . $tel; - } - $format = $res->fetchOneAssoc(); - } - if ($format['format'] == '') { - $format['format'] = '+p'; - } - $j = 0; - $i = strlen($format['phoneprf']); - $length_format = strlen($format['format']); - while (($i < $tel_length) && ($j < $length_format)){ - if ($format['format'][$j] == '#'){ - $ret .= $tel[$i]; - $i++; - } else if ($format['format'][$j] == 'p') { - $ret .= $format['phoneprf']; - } else { - $ret .= $format['format'][$j]; - } - $j++; - } - for (; $i < $tel_length - 1; $i += 2) { - $ret .= ' ' . substr($tel, $i, 2); - } - //appends last alone number to the last block - if ($i < $tel_length) { - $ret .= substr($tel, $i); - } - return $ret; -} - -/** - * Extract search token from term - * @param $term a utf-8 string that can contain any char - * @param an array of elementary tokens - */ -function tokenize_job_term($term) -{ - $term = mb_strtoupper(replace_accent($term)); - $term = str_replace(array('/', ',', '(', ')', '"', '&', '»', '«'), ' ', $term); - $tokens = explode(' ', $term); - static $not_tokens = array('ET','AND','DE','DES','DU','D\'','OU','L\'','LA','LE','LES','PAR','AU','AUX','EN','SUR','UN','UNE','IN'); - foreach ($tokens as &$t) { - if (substr($t, 1, 1) == '\'' && in_array(substr($t, 0, 2), $not_tokens)) { - $t = substr($t, 2); - } - if (strlen($t) == 1 || in_array($t, $not_tokens)) { - $t = false; - continue; - } - } - return array_filter($tokens); -} - -// vim:set et sw=4 sts=4 sws=4 foldmethod=marker enc=utf-8: -?> diff --git a/include/profilefields.inc.php b/include/profilefields.inc.php index 594e327..506b8ac 100644 --- a/include/profilefields.inc.php +++ b/include/profilefields.inc.php @@ -35,6 +35,8 @@ abstract class ProfileField Profile::FETCH_PHONES => 'ProfilePhones', Profile::FETCH_MENTOR_SECTOR => 'ProfileMentoringSectors', Profile::FETCH_MENTOR_COUNTRY => 'ProfileMentoringCountries', + Profile::FETCH_JOB_TERMS => 'ProfileJobTerms', + Profile::FETCH_MENTOR_TERMS => 'ProfileMentoringTerms', ); /** The profile to which this field belongs @@ -92,6 +94,9 @@ class ProfileFieldIterator implements PlIterator public function __construct($cls, array $pids, ProfileVisibility $visibility) { + if (is_numeric($cls) && isset(ProfileField::$fields[$cls])) { + $cls = ProfileField::$fields[$cls]; + } $this->data = call_user_func(array($cls, 'fetchData'), $pids, $visibility); $this->cls = $cls; } @@ -169,6 +174,7 @@ class Job public $company = null; public $phones = array(); public $address = null; + public $terms = array(); public $jobid; @@ -219,6 +225,30 @@ class Job $this->address = $address; } } + + public function addTerm(JobTerm &$term) + { + $this->terms[$term->jtid] = $term; + } +} +// }}} +// {{{ class JobTerm +class JobTerm +{ + public $jtid; + public $full_name; + public $pid; + public $jid; + + /** Fields are: + * pid, jid, jtid, full_name + */ + public function __construct($data) + { + foreach ($data as $key => $val) { + $this->$key = $val; + } + } } // }}} // {{{ class Address @@ -776,9 +806,64 @@ class ProfileJobs extends ProfileField $this->company = $companies[$job->jobid]; } } + + public function addJobTerms(ProfileJobTerms $jobterms) + { + $terms = $jobterms->get(); + foreach ($terms as $term) { + if ($this->pid == $term->pid && array_key_exists($term->jid, $this->jobs)) { + $this->jobs[$term->jid]->addTerm(&$term); + } + } + } } // }}} +// {{{ class ProfileJobTerms [ Field ] +class ProfileJobTerms extends ProfileField +{ + private $jobterms = array(); + public function __construct(PlInnerSubIterator $it) + { + $this->pid = $it->value(); + while ($term = $it->next()) { + $this->jobterms[] = new JobTerm($term); + } + } + + public function get() + { + return $this->jobterms; + } + + public static function fetchData(array $pids, ProfileVisibility $visibility) + { + $data = XDB::iterator('SELECT jt.jtid, jte.full_name, jt.pid, jt.jid + FROM profile_job_term AS jt + INNER JOIN profile_job AS j ON (jt.pid = j.pid AND jt.jid = j.id) + LEFT JOIN profile_job_term_enum AS jte USING(jtid) + WHERE jt.pid IN {?} AND j.pub IN {?} + ORDER BY ' . XDB::formatCustomOrder('jt.pid', $pids), + $pids, $visibility->levels()); + return PlIteratorUtils::subIterator($data, PlIteratorUtils::arrayValueCallback('pid')); + } +} +// }}} +// {{{ class ProfileMentoringTerms [ Field ] +class ProfileMentoringTerms extends ProfileJobTerms +{ + public static function fetchData(array $pids, ProfileVisibility $visibility) + { + $data = XDB::iterator('SELECT mt.jtid, jte.full_name, mt.pid + FROM profile_mentor_term AS mt + LEFT JOIN profile_job_term_enum AS jte USING(jtid) + WHERE mt.pid IN {?} + ORDER BY ' . XDB::formatCustomOrder('mt.pid', $pids), + $pids); + return PlIteratorUtils::subIterator($data, PlIteratorUtils::arrayValueCallback('pid')); + } +} +// }}} // {{{ class CompanyList class CompanyList { diff --git a/include/ufbuilder.inc.php b/include/ufbuilder.inc.php index 4a734b2..819fd91 100644 --- a/include/ufbuilder.inc.php +++ b/include/ufbuilder.inc.php @@ -181,6 +181,7 @@ class UFB_AdvancedSearch extends UserFilterBuilder new UFBF_JobSector('sector', 'Poste'), new UFBF_JobDescription('jobdescription', 'Fonction'), new UFBF_JobCv('cv', 'CV'), + new UFBF_JobTerms('jobterm', 'Mots-clefs'), new UFBF_Nationality('nationaliteTxt', 'nationalite', 'Nationalité'), new UFBF_Binet('binetTxt', 'binet', 'Binet'), @@ -842,6 +843,16 @@ class UFBF_JobSector extends UFBF_Mixed } // }}} +// {{{ class UFBF_JobTerms +class UFBF_JobTerms extends UFBF_Index +{ + protected function buildUFC(UserFilterBuilder &$ufb) + { + return new UFC_Job_Terms($this->val); + } +} +// }}} + // {{{ class UFBF_JobDescription class UFBF_JobDescription extends UFBF_Text { diff --git a/modules/profile.php b/modules/profile.php index 61d1596..f38fbfd 100644 --- a/modules/profile.php +++ b/modules/profile.php @@ -44,6 +44,8 @@ class ProfileModule extends PLModule 'profile/ajax/skill' => $this->make_hook('ajax_skill', AUTH_COOKIE, 'user', NO_AUTH), 'profile/ajax/searchname' => $this->make_hook('ajax_searchname', AUTH_COOKIE, 'user', NO_AUTH), 'profile/ajax/buildnames' => $this->make_hook('ajax_buildnames', AUTH_COOKIE, 'user', NO_AUTH), + 'profile/ajax/tree/jobterms' => $this->make_hook('ajax_tree_job_terms', AUTH_COOKIE, 'user', NO_AUTH), + 'profile/jobterms' => $this->make_hook('jobterms', AUTH_COOKIE, 'user', NO_AUTH), 'javascript/education.js' => $this->make_hook('education_js', AUTH_COOKIE), 'javascript/grades.js' => $this->make_hook('grades_js', AUTH_COOKIE), 'profile/medal' => $this->make_hook('medal', AUTH_PUBLIC), @@ -55,6 +57,7 @@ class ProfileModule extends PLModule 'referent/search' => $this->make_hook('ref_search', AUTH_COOKIE), 'referent/ssect' => $this->make_hook('ref_sect', AUTH_COOKIE, 'user', NO_AUTH), 'referent/country' => $this->make_hook('ref_country', AUTH_COOKIE, 'user', NO_AUTH), + 'referent/autocomplete' => $this->make_hook('ref_autocomplete', AUTH_COOKIE, 'user', NO_AUTH), 'groupes-x' => $this->make_hook('xnet', AUTH_COOKIE), 'groupes-x/logo' => $this->make_hook('xnetlogo', AUTH_PUBLIC), @@ -446,7 +449,6 @@ class ProfileModule extends PLModule $page->assign('jobpref', $jobpref); } } - function handler_ajax_sub_sector(&$page, $id, $ssect, $sssect = -1) { pl_content_headers("text/html"); @@ -459,6 +461,23 @@ class ProfileModule extends PLModule $page->assign('sel', $sssect); } + /** + * Page for url "profile/ajax/tree/jobterms". Display a JSon page containing + * the sub-branches of a branch in the job terms tree. + * @param $page the Platal page + * @param $filter filter helps to display only jobterms that are contained in jobs or in mentors + * + * @param Env::i('jtid') job term id of the parent branch, if none trunk will be used + * @param Env::v('attrfunc') the name of a javascript function that will be called when a branch + * is chosen + * @param Env::v('treeid') tree id that will be given as first argument of attrfunc function + * the second argument will be the chosen job term id and the third one the chosen job full name. + */ + function handler_ajax_tree_job_terms(&$page, $filter = JobTerms::ALL) + { + JobTerms::ajaxGetBranch(&$page, $filter); + } + function handler_ajax_alternates(&$page, $id, $sssect) { pl_content_headers("text/html"); @@ -566,18 +585,12 @@ class ProfileModule extends PLModule $page->setTitle('Emploi et Carrières'); - // Retrieval of sector names - $sectors = XDB::fetchAllAssoc('id', 'SELECT pjse.id, pjse.name - FROM profile_job_sector_enum AS pjse - INNER JOIN profile_mentor_sector AS pms ON (pms.sectorid = pjse.id) - GROUP BY pjse.id - ORDER BY pjse.name'); - $page->assign_by_ref('sectors', $sectors); - // nb de mentors - $res = XDB::query("SELECT count(*) FROM profile_mentor"); + $res = XDB::query("SELECT count(distinct pid) FROM profile_mentor_term"); $page->assign('mentors_number', $res->fetchOneCell()); + $page->addJsLink('jquery.autocomplete.js'); + // On vient d'un formulaire require_once 'ufbuilder.inc.php'; $ufb = new UFB_MentorSearch(); @@ -627,6 +640,85 @@ class ProfileModule extends PLModule $page->assign('list', $it); } + /** + * Page for url "referent/autocomplete". Display an "autocomplete" page (plain/text with values + * separated by "|" chars) for jobterms in referent (mentor) search. + * @see handler_jobterms + */ + function handler_ref_autocomplete(&$page) + { + $this->handler_jobterms(&$page, 'mentor'); + } + + /** + * Page for url "profile/jobterms" (function also used for "referent/autocomplete" @see + * handler_ref_autocomplete). Displays an "autocomplete" page (plain text with values + * separated by "|" chars) for jobterms to add in profile. + * @param $page the Platal page + * @param $type set to 'mentor' to display the number of mentors for each term and order + * by descending number of mentors. + * + * @param Env::v('q') the text that has been typed and to complete automatically + */ + function handler_jobterms(&$page, $type = 'nomentor') + { + pl_content_headers("text/plain"); + + $q = Env::v('q').'%'; + $tokens = JobTerms::tokenize($q); + if (count($tokens) == 0) { + exit; + } + sort($tokens); + $q_normalized = implode(' ', $tokens); + + // try to look in cached results + $cache = XDB::query('SELECT result + FROM search_autocomplete + WHERE name = {?} AND + query = {?} AND + generated > NOW() - INTERVAL 1 DAY', + $type, $q_normalized); + if ($res = $cache->fetchOneCell()) { + echo $res; + die(); + } + + $joins = JobTerms::token_join_query($tokens, 'e'); + if ($type == 'mentor') { + $count = ', COUNT(DISTINCT pid) AS nb'; + $countjoin = ' LEFT JOIN profile_job_term_relation AS r ON(r.jtid_1 = e.jtid) LEFT JOIN profile_mentor_term AS m ON(r.jtid_2 = m.jtid)'; + $countorder = 'nb DESC, '; + } else { + $count = $countjoin = $countorder = ''; + } + $list = XDB::iterator('SELECT e.jtid AS id, e.full_name AS field'.$count.' + FROM profile_job_term_enum AS e '.$joins.$countjoin.' + GROUP BY e.jtid + ORDER BY '.$countorder.'field + LIMIT 11'); + $nbResults = 0; + $res = ''; + while ($result = $list->next()) { + $nbResults++; + if ($nbResults == 11) { + $res .= $q."|-1\n"; + } else { + $res .= $result['field'].'|'; + if ($count) { + $res .= $result['nb'].'|'; + } + $res .= $result['id']; + } + $res .= "\n"; + } + XDB::query('REPLACE INTO search_autocomplete + VALUES ({?}, {?}, {?}, NOW())', + $type, $q_normalized, $res); + echo $res; + exit(); + } + function handler_xnet(&$page) { $page->changeTpl('profile/groupesx.tpl'); diff --git a/modules/profile/jobs.inc.php b/modules/profile/jobs.inc.php index ccd7d0d..30cc3f4 100644 --- a/modules/profile/jobs.inc.php +++ b/modules/profile/jobs.inc.php @@ -109,7 +109,8 @@ class ProfileSettingJob extends ProfileSettingGeocoding 'tel' => '', 'pub' => 'private', 'comment' => '', - )), + ), + 'terms' => array()), ); } @@ -147,6 +148,26 @@ class ProfileSettingJob extends ProfileSettingGeocoding list($job['sector'], $job['subSector'], $job['subSubSector']) = $res->fetchOneRow(); } } + if (count($job['terms'])) { + $termsid = array(); + foreach ($job['terms'] as $term) { + if (!$term['full_name']) { + $termsid[] = $term['jtid']; + } + } + if (count($termsid)) { + $res = XDB::query("SELECT jtid, full_name + FROM profile_job_term_enum + WHERE jtid IN {?}", + $termsid); + $term_id_to_name = $res->fetchAllAssoc('jtid', false); + foreach ($job['terms'] as &$term) { + if (!$term['full_name']) { + $term['full_name'] = $term_id_to_name[$term['jtid']]; + } + } + } + } if ($job['name']) { $res = XDB::query("SELECT id FROM profile_job_enum @@ -233,6 +254,7 @@ class ProfileSettingJob extends ProfileSettingGeocoding WHERE pid = {?} AND type = 'job'", $page->pid()); Phone::deletePhones($page->pid(), Phone::LINK_JOB); + $terms_values = array(); foreach ($value as $id => &$job) { if (isset($job['name']) && $job['name']) { if (isset($job['jobid']) && $job['jobid']) { @@ -251,8 +273,17 @@ class ProfileSettingJob extends ProfileSettingGeocoding $address = new ProfileSettingAddress(); $address->saveAddress($page->pid(), $id, $job['w_address'], 'job'); Phone::savePhones($job['w_phone'], $page->pid(), Phone::LINK_JOB, $id); + if (isset($job['terms'])) { + foreach ($job['terms'] as $term) { + $terms_values[] = '('.XDB::escape($page->pid()).', '. XDB::escape($id).', '.XDB::escape($term['jtid']).', "original")'; + } + } } } + if (count($terms_values) > 0) { + XDB::execute('INSERT INTO profile_job_term (pid, jid, jtid, computed) + VALUES '.implode(', ', $terms_values)); + } } public function getText($value) { @@ -418,6 +449,32 @@ class ProfileSettingJobs extends ProfilePage while ($phone = $it->next()) { $this->values['jobs'][$phone->linkId()]['w_phone'][$phone->id()] = $phone->toFormArray(); } + $res = XDB::iterator("SELECT e.jtid, e.full_name, j.jid AS jobid + FROM profile_job_term_enum AS e + INNER JOIN profile_job_term AS j USING(jtid) + WHERE pid = {?} + ORDER BY j.jid", + $this->pid()); + $i = 0; + $jobNb = count($this->values['jobs']); + while ($term = $res->next()) { + $jobid = $term['jobid']; + while ($i < $jobNb && $this->values['jobs'][$i]['id'] < $jobid) { + $i++; + } + if ($i >= $jobNb) { + break; + } + $job =& $this->values['jobs'][$i]; + if ($job['id'] != $jobid) { + continue; + } + if (!isset($job['terms'])) { + $job['terms'] = array(); + } + $job['terms'][] = $term; + } + foreach ($this->values['jobs'] as $id => &$job) { $phone = new Phone(); if (!isset($job['w_phone'])) { diff --git a/modules/profile/mentor.inc.php b/modules/profile/mentor.inc.php index 17d9cdd..3aa4140 100644 --- a/modules/profile/mentor.inc.php +++ b/modules/profile/mentor.inc.php @@ -19,31 +19,42 @@ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ***************************************************************************/ -class ProfileSettingSectors implements ProfileSetting +/** Terms associated to profile mentoring */ +class ProfileSettingTerms implements ProfileSetting { public function value(ProfilePage &$page, $field, $value, &$success) { $success = true; if (is_null($value)) { $value = array(); - $res = XDB::iterRow("SELECT m.sectorid, m.subsectorid, ss.name - FROM profile_mentor_sector AS m - INNER JOIN profile_job_sector_enum AS s ON (m.sectorid = s.id) - INNER JOIN profile_job_subsector_enum AS ss ON (s.id = ss.sectorid AND m.subsectorid = ss.id) - WHERE m.pid = {?}", + $res = XDB::query('SELECT e.jtid, e.full_name + FROM profile_mentor_term AS m + INNER JOIN profile_job_term_enum AS e ON (m.jtid = e.jtid) + WHERE m.pid = {?}', $page->pid()); - while (list($s, $ss, $ssname) = $res->next()) { - if (!isset($value[$s])) { - $value[$s] = array($ss => $ssname); - } else { - $value[$s][$ss] = $ssname; - } - } + $value = $res->fetchAllAssoc(); } elseif (!is_array($value)) { $value = array(); - } elseif (count($value) > 10) { - Platal::page()->trigError("Le nombre de secteurs d'expertise est limité à 10."); + } elseif (count($value) > 20) { + Platal::page()->trigError("Le nombre de mots clefs d'expertise est limité à 20."); $success = false; + } else { + $missing_full_names = array(); + foreach ($value as &$term) if (empty($term['full_name'])) { + $missing_full_names[] = $term['jtid']; + } + if (count($missing_full_names)) { + $res = XDB::query('SELECT jtid, full_name + FROM profile_job_term_enum + WHERE jtid IN {?}', + $missing_full_names); + $term_id_to_name = $res->fetchAllAssoc('jtid', false); + foreach ($value as &$term) { + if (empty($term['full_name'])) { + $term['full_name'] = $term_id_to_name[$term['jtid']]; + } + } + } } ksort($value); foreach ($value as &$sss) { @@ -55,29 +66,27 @@ class ProfileSettingSectors implements ProfileSetting public function save(ProfilePage &$page, $field, $value) { - XDB::execute("DELETE FROM profile_mentor_sector + XDB::execute("DELETE FROM profile_mentor_term WHERE pid = {?}", $page->pid()); if (!count($value)) { return; } - foreach ($value as $id => $sect) { - foreach ($sect as $sid => $name) { - XDB::execute("INSERT INTO profile_mentor_sector (pid, sectorid, subsectorid) - VALUES ({?}, {?}, {?})", - $page->pid(), $id, $sid); - } + $mentor_term_values = array(); + foreach ($value as &$term) { + $mentor_term_values[] = '('.XDB::escape($page->pid()).', '.XDB::escape($term['jtid']).')'; } + XDB::execute('INSERT INTO profile_mentor_term (pid, jtid) + VALUES '.implode(',', $mentor_term_values)); + } public function getText($value) { - $sectors = array(); - foreach ($value as $sector) { - foreach ($sector as $subsector) { - $sectors[] = $subsector; - } + $terms = array(); + foreach ($value as &$term) { + $terms[] = $term['full_name']; } - return implode(', ', $sectors); + return implode(', ', $terms); } } @@ -132,7 +141,7 @@ class ProfileSettingMentor extends ProfilePage { parent::__construct($wiz); $this->settings['expertise'] = null; - $this->settings['sectors'] = new ProfileSettingSectors(); + $this->settings['terms'] = new ProfileSettingTerms(); $this->settings['countries'] = new ProfileSettingCountry(); } @@ -165,9 +174,6 @@ class ProfileSettingMentor extends ProfilePage public function _prepare(PlPage &$page, $id) { - $page->assign('sectorList', XDB::iterator('SELECT id, name - FROM profile_job_sector_enum')); - $page->assign('countryList', XDB::iterator("SELECT iso_3166_1_a2, countryFR FROM geoloc_countries ORDER BY countryFR")); diff --git a/modules/search.php b/modules/search.php index cc12371..2796ed1 100644 --- a/modules/search.php +++ b/modules/search.php @@ -218,7 +218,7 @@ class SearchModule extends PLModule 'city' => DirEnum::LOCALITIES, 'countryTxt' => DirEnum::COUNTRIES, 'entreprise' => DirEnum::COMPANIES, - 'secteurTxt' => DirEnum::SECTORS, + 'jobtermTxt' => DirEnum::JOBTERMS, 'description' => DirEnum::JOBDESCRIPTION, 'nationaliteTxt' => DirEnum::NATIONALITIES, 'schoolTxt' => DirEnum::EDUSCHOOLS, @@ -304,6 +304,22 @@ class SearchModule extends PLModule case 'secteur': $ids = DirEnum::getOptionsIter(DirEnum::SECTORS); break; + case 'jobterm': + if (Env::has('jtid')) { + JobTerms::ajaxGetBranch(&$page, JobTerms::ONLY_JOBS); + return; + } else { + pl_content_headers('text/xml'); + echo '
    '; // global container so that response is valid xml + echo ''; + echo ''; + echo '
    '; // container where to create the tree + echo ''; + echo ''; + echo ''; + echo '
    '; + exit(); + } default: exit(); } if (isset($idVal)) { diff --git a/templates/include/emploi.tpl b/templates/include/emploi.tpl index 66b5614..a322c8d 100644 --- a/templates/include/emploi.tpl +++ b/templates/include/emploi.tpl @@ -30,10 +30,14 @@ {if $job->user_site} [Page perso]{/if} {/if} - {if $job->sector} + {if count($job->terms)} - Secteur : - {$job->sector}{if $job->subsector} ({$job->subsector}){/if} + Mots-clefs : + {/if} diff --git a/templates/include/jobterms.branch.tpl b/templates/include/jobterms.branch.tpl new file mode 100644 index 0000000..14d6bb9 --- /dev/null +++ b/templates/include/jobterms.branch.tpl @@ -0,0 +1,41 @@ +{**************************************************************************} +{* *} +{* Copyright (C) 2003-2010 Polytechnique.org *} +{* http://opensource.polytechnique.org/ *} +{* *} +{* This program is free software; you can redistribute it and/or modify *} +{* it under the terms of the GNU General Public License as published by *} +{* the Free Software Foundation; either version 2 of the License, or *} +{* (at your option) any later version. *} +{* *} +{* This program is distributed in the hope that it will be useful, *} +{* but WITHOUT ANY WARRANTY; without even the implied warranty of *} +{* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *} +{* GNU General Public License for more details. *} +{* *} +{* You should have received a copy of the GNU General Public License *} +{* along with this program; if not, write to the Free Software *} +{* Foundation, Inc., *} +{* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *} +{* *} +{**************************************************************************} +[ +{iterate from=$subTerms item=term} + {if $started},{/if} + {assign var=started value=1} + {ldelim} + "data" : + {ldelim} + "title" : "{$term.name|replace:'"':'\\"'}{if $filter} ({$term.nb} {$filter}{if $term.nb > 1}s{/if}){/if}", + "attr" : {ldelim} + {if $attrfunc}"href" : "javascript:{$attrfunc}('{$treeid}','{$term.jtid}',\"{$term.full_name|replace:'"':'\\\\\\"'}\")",{/if} + "title" : "{$term.full_name|replace:'"':'\\"'}" + {rdelim} + {rdelim}, + "attr" : {ldelim} "id" : "job_terms_tree_{$treeid}_{$term.jtid}" {rdelim}, + "state": "closed" + {rdelim} +{/iterate} +] + +{* vim:set et sw=2 sts=2 sws=2 enc=utf-8: *} diff --git a/templates/profile/fiche_referent.tpl b/templates/profile/fiche_referent.tpl index 0ded2bc..a35e521 100644 --- a/templates/profile/fiche_referent.tpl +++ b/templates/profile/fiche_referent.tpl @@ -21,7 +21,7 @@ {**************************************************************************} {javascript name=ajax} -{assign var=sectors value=$profile->getMentoringSectors()} +{assign var=terms value=$profile->getMentoringTerms()} {assign var=countries value=$profile->getMentoringCountries()}
    @@ -33,7 +33,7 @@
    - {if $profile->expertise != '' || $sectors|count || $countries|count } + {if $profile->expertise != '' || $terms|count || $countries|count }

    Informations de référent :

    {if $profile->expertise} @@ -42,12 +42,12 @@ {$profile->expertise|nl2br}
    {/if} - {if $sectors|count} + {if $terms|count}
    - Secteurs :
    + Mots-clefs :
      - {foreach from=$sectors item="sector" key="i"} -
    • {$sector.sector}{if $sector.subsector != ''} ({$sector.subsector}){/if}
    • + {foreach from=$terms item="term"} +
    • {$term->full_name}
    • {/foreach}
    diff --git a/templates/profile/jobs.job.tpl b/templates/profile/jobs.job.tpl index 118ad93..97220a7 100644 --- a/templates/profile/jobs.job.tpl +++ b/templates/profile/jobs.job.tpl @@ -20,6 +20,7 @@ {* *} {**************************************************************************} + {assign var=jobid value="job_"|cat:$i} {assign var=jobpref value="jobs[`$i`]"} {assign var=sector_text value="sector_text_"|cat:$i} @@ -116,11 +117,31 @@ Ta place dans l'entreprise - Secteur d'activité - - - {icon name="table" title="Tous les secteurs"} + Mots-clefs + + + {icon name="table" title="Tous les mots-clefs"} + + + + + diff --git a/templates/profile/jobs.tpl b/templates/profile/jobs.tpl index 2139d39..1146519 100644 --- a/templates/profile/jobs.tpl +++ b/templates/profile/jobs.tpl @@ -20,6 +20,7 @@ {* *} {**************************************************************************} +{javascript name=jobtermstree} {foreach from=$jobs item=job key=i} {include file="profile/jobs.job.tpl" i=$i job=$job new=false} {/foreach} diff --git a/templates/profile/mentor.tpl b/templates/profile/mentor.tpl index 44ea740..56951e6 100644 --- a/templates/profile/mentor.tpl +++ b/templates/profile/mentor.tpl @@ -20,6 +20,8 @@ {* *} {**************************************************************************} +{javascript name=jobtermstree} +
    {icon name=information title="Afficher ma fiche référent"}Tu peux consulter ta fiche référent qui n'est accessible que par les X.
    {if (!$expertise)||(!($sectors|@count))} @@ -82,48 +84,51 @@ + + - - + - + - - +
    +
    {icon name="flag_red" value="privé"}
    - Secteurs d'activité dans lesquels tu as beaucoup exercé + Mots clefs qui représentent le mieux ton expérience
    -
    Secteur
    - -
    + Il est préférable de mentionner des notions précises : Pizzaïolo plutôt que Hôtellerie. + En effet Les recherches sur le mot-clef Hôtellerie te trouveront dans les deux cas mais une + recherche sur Production culinaire ou Pizzaïolo non. +
    -
    Sous-secteur
    - +
    Mots-clefs + + {icon name="table" title="Tous les mots-clefs"} +
    - {if $sectors|@count} - {foreach from=$sectors item=sector key=s} - {foreach from=$sector item=subSector key=ss} - - {/foreach} - {/foreach} - {/if} +
    diff --git a/templates/profile/referent.tpl b/templates/profile/referent.tpl index 5c04863..0da1170 100644 --- a/templates/profile/referent.tpl +++ b/templates/profile/referent.tpl @@ -34,6 +34,8 @@ Actuellement, {$mentors_number} mentors et référents se sont déclarés sur {#

    {javascript name=ajax} +{javascript name=jquery.jstree} +{javascript name=jobtermstree} @@ -71,35 +79,17 @@ function setSSectors() - - - - - - - - - - - +
    - Secteur de compétence
    du référent + Mot-clef :
    - + + + {icon name=table title="Tous les mots-clefs"}
    @@ -109,6 +99,17 @@ function setSSectors() {* vim:set et sw=2 sts=2 sws=2 enc=utf-8: *} diff --git a/templates/search/adv.form.tpl b/templates/search/adv.form.tpl index ff73a1c..5521a00 100644 --- a/templates/search/adv.form.tpl +++ b/templates/search/adv.form.tpl @@ -84,6 +84,13 @@ }); } + // when choosing a job term in tree, hide tree and set job term field + function searchForJobTerm(treeid, jtid, full_name) { + $(".term_tree").remove(); + $("input[name='jobtermTxt']").val(full_name).addClass("hidden_valid").show(); + $("input[name='jobterm']").val(jtid); + } + // when choosing autocomplete from list, must validate function select_autocomplete(name) { nameRealField = name.replace(/Txt$/, ''); @@ -305,12 +312,12 @@ - Secteur + Mots-clefs - - - {icon name="table" title="Tous les secteurs"} + + + {icon name="table" title="Tous les mots-clefs"} diff --git a/upgrade/1.0.1/tokenize_job_terms.php b/upgrade/1.0.1/tokenize_job_terms.php index 31bea52..bbe2c5f 100755 --- a/upgrade/1.0.1/tokenize_job_terms.php +++ b/upgrade/1.0.1/tokenize_job_terms.php @@ -1,13 +1,13 @@ #!/usr/bin/php5 debug = 0; //do not store backtraces $terms = XDB::iterator('SELECT `jtid`, `name` FROM `profile_job_term_enum`'); while ($term = $terms->next()) { - $tokens = array_unique(tokenize_job_term($term['name'])); + $tokens = array_unique(JobTerms::tokenize($term['name'])); if (!count($tokens)) { continue; }