model.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622
  1. <?php
  2. /**
  3. * The model file of job module of ZenTaoPMS.
  4. *
  5. * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
  6. * @license ZPL(http://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
  7. * @author Yidong Wang <yidong@cnezsoft.com>
  8. * @package job
  9. * @version $Id$
  10. * @link https://www.zentao.net
  11. * @property jobTao $jobTao
  12. */
  13. class jobModel extends model
  14. {
  15. /**
  16. * 根据id获取流水线。
  17. * Get by id.
  18. *
  19. * @param int $id
  20. * @access public
  21. * @return object
  22. */
  23. public function getByID($id)
  24. {
  25. $job = $this->dao->select('*')->from(TABLE_JOB)->where('id')->eq($id)->fetch();
  26. if(empty($job)) return new stdClass();
  27. if(strtolower($job->engine) == 'gitlab')
  28. {
  29. $pipeline = json_decode($job->pipeline);
  30. if(!isset($pipeline->reference)) return $job;
  31. $job->project = $pipeline->project;
  32. $job->reference = $pipeline->reference;
  33. }
  34. elseif($job->engine == 'jenkins')
  35. {
  36. if(strpos($job->pipeline, '/job/') === 0)
  37. {
  38. $job->rawPipeline = $job->pipeline;
  39. $job->pipeline = trim(substr($job->pipeline, 5), '/');
  40. }
  41. }
  42. return $job;
  43. }
  44. /**
  45. * 获取流水线列表。
  46. * Get job list.
  47. *
  48. * @param int $repoID
  49. * @param string $jobQuery
  50. * @param string $orderBy
  51. * @param object $pager
  52. * @param string $engine
  53. * @param string $pipeline
  54. * @access public
  55. * @return array
  56. */
  57. public function getList($repoID = 0, $jobQuery = '', $orderBy = 'id_desc', $pager = null, $engine = '', $pipeline = '')
  58. {
  59. return $this->dao->select('t1.*, t2.name as repoName, t3.name as jenkinsName')->from(TABLE_JOB)->alias('t1')
  60. ->leftJoin(TABLE_REPO)->alias('t2')->on('t1.repo=t2.id')
  61. ->leftJoin(TABLE_PIPELINE)->alias('t3')->on('t1.server=t3.id')
  62. ->where('t1.deleted')->eq('0')
  63. ->beginIF($repoID)->andWhere('t1.repo')->eq($repoID)->fi()
  64. ->beginIF($engine)->andWhere('t1.engine')->eq($engine)->fi()
  65. ->beginIF($pipeline)->andWhere('t1.pipeline')->eq($pipeline)->fi()
  66. ->beginIF(!empty($jobQuery))->andWhere($jobQuery)->fi()
  67. ->orderBy($orderBy)
  68. ->page($pager)
  69. ->fetchAll('id');
  70. }
  71. /**
  72. * 获取流水线列表根据版本库ID。
  73. * Get job list by RepoID.
  74. *
  75. * @param int $repoID
  76. * @access public
  77. * @return array
  78. */
  79. public function getListByRepoID($repoID)
  80. {
  81. return $this->dao->select('id, name, lastStatus')->from(TABLE_JOB)
  82. ->where('deleted')->eq('0')
  83. ->andWhere('repo')->eq($repoID)
  84. ->orderBy('id_desc')
  85. ->fetchAll('id');
  86. }
  87. /**
  88. * 获取流水线键值对根据版本库ID。
  89. * Get job pairs by RepoID.
  90. *
  91. * @param int $repoID
  92. * @param string $engine gitlab|jenkins
  93. * @access public
  94. * @return array
  95. */
  96. public function getPairs($repoID, $engine = '')
  97. {
  98. return $this->dao->select('id, name')->from(TABLE_JOB)
  99. ->where('deleted')->eq('0')
  100. ->andWhere('repo')->eq($repoID)
  101. ->beginIF($engine)->andWhere('engine')->eq($engine)->fi()
  102. ->orderBy('id_desc')
  103. ->fetchPairs();
  104. }
  105. /**
  106. * Get list by triggerType field.
  107. *
  108. * @param string $triggerType
  109. * @param array $repoIdList
  110. * @access public
  111. * @return array
  112. */
  113. public function getListByTriggerType($triggerType, $repoIdList = array())
  114. {
  115. return $this->dao->select('*')->from(TABLE_JOB)
  116. ->where('deleted')->eq('0')
  117. ->andWhere('triggerType')->like('%' . $triggerType . '%')
  118. ->beginIF($repoIdList)->andWhere('repo')->in($repoIdList)->fi()
  119. ->fetchAll('id');
  120. }
  121. /**
  122. * Get trigger config.
  123. *
  124. * @param object $job
  125. * @access public
  126. * @return string
  127. */
  128. public function getTriggerConfig($job)
  129. {
  130. $triggerList = array();
  131. if(strpos($job->triggerType, 'tag') !== false)
  132. {
  133. $triggerType = $this->lang->job->triggerTypeList['tag'];
  134. if(!empty($job->svnDir)) $triggerType = $this->lang->job->dirChange . "({$job->svnDir})";
  135. $triggerList[] = $triggerType;
  136. }
  137. if(strpos($job->triggerType, 'commit') !== false) $triggerList[] = "{$this->lang->job->triggerTypeList['commit']}({$job->comment})";
  138. if(strpos($job->triggerType, 'schedule') !== false)
  139. {
  140. $atDay = '';
  141. foreach(explode(',', $job->atDay) as $day) $atDay .= zget($this->lang->datepicker->dayNames, trim($day), '') . ',';
  142. $atDay = trim($atDay, ',');
  143. $triggerList[] = "{$this->lang->job->triggerTypeList['schedule']}({$atDay}, {$job->atTime})";
  144. }
  145. return implode('; ', $triggerList);
  146. }
  147. /**
  148. * Get trigger group.
  149. *
  150. * @param string $triggerType
  151. * @param array $repoIdList
  152. * @access public
  153. * @return array
  154. */
  155. public function getTriggerGroup($triggerType, $repoIdList)
  156. {
  157. $jobs = $this->getListByTriggerType($triggerType, $repoIdList);
  158. $group = array();
  159. foreach($jobs as $job) $group[$job->repo][$job->id] = $job;
  160. return $group;
  161. }
  162. /**
  163. * Create a job.
  164. *
  165. * @param object $job
  166. * @access public
  167. * @return int|bool
  168. */
  169. public function create($job)
  170. {
  171. $repo = $this->loadModel('repo')->getByID($job->repo);
  172. $job = $this->jobTao->getServerAndPipeline($job, $repo);
  173. if(dao::isError()) return false;
  174. $result = $this->jobTao->checkIframe($job);
  175. if(!$result) return false;
  176. $this->dao->insert(TABLE_JOB)->data($job)
  177. ->batchCheck($this->config->job->create->requiredFields, 'notempty')
  178. ->batchCheckIF($job->frame === 'sonarqube', "sonarqubeServer,projectKey", 'notempty')
  179. ->exec();
  180. if(dao::isError()) return false;
  181. return $this->dao->lastInsertId();
  182. }
  183. /**
  184. * Update a job.
  185. *
  186. * @param int $id
  187. * @access public
  188. * @return bool
  189. * @param object $job
  190. */
  191. public function update($id, $job)
  192. {
  193. $repo = $this->loadModel('repo')->getByID($job->repo);
  194. if($this->app->rawMethod != 'trigger')
  195. {
  196. $job = $this->jobTao->getServerAndPipeline($job, $repo);
  197. if(dao::isError()) return false;
  198. $result = $this->jobTao->checkIframe($job, $id);
  199. if(!$result) return false;
  200. }
  201. else
  202. {
  203. $this->jobTao->getSvnDir($job, $repo);
  204. $result = $this->jobTao->getCustomParam($job);
  205. if(!$result) return false;
  206. }
  207. $skipFields = 'triggerType,svnDir,comment,atDay,atTime,paramName,paramValue,autoRun';
  208. if($this->app->rawMethod == 'trigger') $skipFields = 'name,engine,repo,reference,frame,product,sonarqubeServer,projectKey,jkServer,jkTask,gitfoxpipeline';
  209. $this->dao->update(TABLE_JOB)->data($job, $skipFields)
  210. ->batchCheckIF($this->app->rawMethod != 'trigger', $this->config->job->edit->requiredFields, 'notempty')
  211. ->batchCheckIF(strpos($job->triggerType, 'schedule') !== false && $job->atDay !== '0', "atDay", 'notempty')
  212. ->batchCheckIF(strpos($job->triggerType, 'schedule') !== false, "atTime", 'notempty')
  213. ->batchCheckIF(strpos($job->triggerType, 'commit') !== false, "comment", 'notempty')
  214. ->batchCheckIF(strpos($job->triggerType, 'action') !== false, "triggerActions", 'notempty')
  215. ->batchCheckIF(($repo->SCM == 'Subversion' && strpos($job->triggerType, 'tag') !== false), "svnDir", 'notempty')
  216. ->batchCheckIF($job->frame === 'sonarqube', "sonarqubeServer,projectKey", 'notempty')
  217. ->where('id')->eq($id)
  218. ->exec();
  219. if(dao::isError()) return false;
  220. $this->initJob($id, $job);
  221. return true;
  222. }
  223. /**
  224. * 创建或者更新流水线的时候初始化工作。
  225. * Init when create or update job.
  226. *
  227. * @param int $id
  228. * @param object $job
  229. * @access public
  230. * @return bool
  231. */
  232. public function initJob($id, $job)
  233. {
  234. if(empty($id) || empty($job->triggerType)) return false;
  235. if(strpos($job->triggerType, 'schedule') !== false && strpos($job->atDay, date('w')) !== false)
  236. {
  237. $compiles = $this->dao->select('*')->from(TABLE_COMPILE)->where('job')->eq($id)->andWhere('LEFT(createdDate, 10)')->eq(date('Y-m-d'))->fetchAll();
  238. foreach($compiles as $compile)
  239. {
  240. if(!empty($compile->status)) continue;
  241. $this->dao->delete()->from(TABLE_COMPILE)->where('id')->eq($compile->id)->exec();
  242. }
  243. $this->loadModel('compile')->createByJob($id, $job->atTime, 'atTime');
  244. }
  245. if(strpos($job->triggerType, 'tag') !== false)
  246. {
  247. $repo = $this->loadModel('repo')->getByID($job->repo);
  248. if(!$repo) return false;
  249. $lastTag = $this->getLastTagByRepo($repo, $job);
  250. $this->updateLastTag($id, $lastTag);
  251. }
  252. return true;
  253. }
  254. /**
  255. * 执行流水线。
  256. * Exec job.
  257. *
  258. * @param int $id
  259. * @param array $extraParam
  260. * @param string $triggerType commit|tag|schedule
  261. * @access public
  262. * @return object|false
  263. */
  264. public function exec($id, $extraParam = array(), $triggerType = '')
  265. {
  266. $job = $this->dao->select('t1.*,t2.name as jenkinsName,t2.url,t2.account,t2.token,t2.password')
  267. ->from(TABLE_JOB)->alias('t1')
  268. ->leftJoin(TABLE_PIPELINE)->alias('t2')->on('t1.server=t2.id')
  269. ->where('t1.id')->eq($id)
  270. ->fetch();
  271. if(!$job) return false;
  272. if(($this->app->rawModule != 'job' || $this->app->rawMethod != 'exec') && !in_array($this->app->rawModule, array('mr', 'sonarqube')) && !empty($job->autoRun)) return false;
  273. $repo = $this->loadModel('repo')->getByID($job->repo);
  274. if(!$repo) return false;
  275. $method = 'exec' . ucfirst($job->engine) . 'Pipeline';
  276. if(!method_exists($this, $method)) return false;
  277. $compileID = 0;
  278. if(in_array($triggerType, array('', 'schedule')) && strpos($job->triggerType, 'schedule') !== false)
  279. {
  280. $compileID = $this->loadModel('compile')->createByJob($job->id, $job->atTime, 'atTime');
  281. }
  282. if(!$compileID && in_array($triggerType, array('', 'tag')) && strpos($job->triggerType, 'tag') !== false)
  283. {
  284. $job->lastTag = $this->getLastTagByRepo($repo, $job);
  285. $tag = '';
  286. if($job->lastTag)
  287. {
  288. $tag = $job->lastTag;
  289. $this->updateLastTag($job->id, $job->lastTag);
  290. }
  291. $compileID = $this->loadModel('compile')->createByJob($job->id, $tag, 'tag');
  292. }
  293. if(!$compileID && in_array($triggerType, array('', 'commit')) && (!$job->triggerType || strpos($job->triggerType, 'commit') !== false))
  294. {
  295. $compileID = $this->loadModel('compile')->createByJob($job->id);
  296. }
  297. $compile = $this->$method($job, $repo, $compileID, $extraParam);
  298. $compile->updateDate = helper::now();
  299. $this->dao->update(TABLE_COMPILE)->data($compile)->where('id')->eq($compileID)->exec();
  300. $this->dao->update(TABLE_JOB)
  301. ->set('lastExec')->eq(helper::now())
  302. ->set('lastStatus')->eq($compile->status)
  303. ->where('id')->eq($job->id)
  304. ->exec();
  305. $compile->id = $compileID;
  306. return $compile;
  307. }
  308. /**
  309. * 执行jenkins流水线。
  310. * Exec jenkins pipeline.
  311. *
  312. * @param object $job
  313. * @param object $repo
  314. * @param int $compileID
  315. * @param array $extraParam
  316. * @access public
  317. * @return object
  318. */
  319. public function execJenkinsPipeline($job, $repo, $compileID, $extraParam = array())
  320. {
  321. $pipeline = new stdclass();
  322. $pipeline->PARAM_TAG = '';
  323. $pipeline->ZENTAO_DATA = "compile={$compileID}";
  324. if(strpos($job->triggerType, 'tag') !== false) $pipeline->PARAM_TAG = $job->lastTag;
  325. /* Add custom parameters to the data. */
  326. if(!empty($job->customParam))
  327. {
  328. foreach(json_decode($job->customParam) as $paramName => $paramValue)
  329. {
  330. $paramValue = str_replace('$zentao_version', $this->config->version, $paramValue);
  331. $paramValue = str_replace('$zentao_account', $this->app->user->account, $paramValue);
  332. $paramValue = str_replace('$zentao_product', (string)$job->product, $paramValue);
  333. $paramValue = str_replace('$zentao_repopath', $repo->path, $paramValue);
  334. $pipeline->$paramName = $paramValue;
  335. }
  336. }
  337. foreach($extraParam as $paramName => $paramValue)
  338. {
  339. if(!isset($pipeline->$paramName)) $pipeline->$paramName = $paramValue;
  340. }
  341. $url = $this->loadModel('compile')->getBuildUrl($job);
  342. $compile = new stdclass();
  343. $compile->id = $compileID;
  344. $compile->queue = $this->loadModel('ci')->sendRequest($url->url, $pipeline, $url->userPWD);
  345. $compile->status = $compile->queue ? 'created' : 'create_fail';
  346. return $compile;
  347. }
  348. /**
  349. * 执行gitlab流水线。
  350. * Exec gitlab pipeline.
  351. *
  352. * @param object $job
  353. * @access public
  354. * @return object
  355. */
  356. public function execGitlabPipeline($job)
  357. {
  358. $pipeline = json_decode($job->pipeline);
  359. /* Set pipeline run branch. */
  360. $pipelineParams = new stdclass;
  361. $pipelineParams->ref = zget($pipeline, 'reference', '');
  362. if(empty($pipelineParams->ref) && !empty($pipeline->project))
  363. {
  364. $project = $this->loadModel('gitlab')->apiGetSingleProject($job->server, (int)$pipeline->project, false);
  365. $pipelineParams->ref = zget($project, 'default_branch', 'master');
  366. $pipeline->reference = $pipelineParams->ref;
  367. $this->dao->update(TABLE_JOB)->set('pipeline')->eq(json_encode($pipeline))->where('id')->eq($job->id)->exec();
  368. }
  369. /* Set pipeline params. */
  370. $customParams = json_decode($job->customParam);
  371. $variables = array();
  372. if($customParams)
  373. {
  374. foreach($customParams as $paramName => $paramValue)
  375. {
  376. $variable = array();
  377. $variable['key'] = $paramName;
  378. $variable['value'] = $paramValue;
  379. $variable['variable_type'] = "env_var";
  380. $variables[] = $variable;
  381. }
  382. }
  383. if(!empty($variables)) $pipelineParams->variables = $variables;
  384. /* Run pipeline. */
  385. $compile = new stdclass();
  386. $pipeline = (object)$this->loadModel('gitlab')->apiCreatePipeline($job->server, (int)zget($pipeline, 'project', 0), $pipelineParams);
  387. if(empty($pipeline->id))
  388. {
  389. $this->gitlab->apiErrorHandling($pipeline);
  390. $compile->status = 'create_fail';
  391. }
  392. else
  393. {
  394. $compile->queue = $pipeline->id;
  395. $compile->status = zget($pipeline, 'status', 'create_fail');
  396. }
  397. return $compile;
  398. }
  399. /**
  400. * 获取版本库最新tag。
  401. * Get last tag of one repo.
  402. *
  403. * @param object $repo
  404. * @param object $job
  405. * @access public
  406. * @return string
  407. */
  408. public function getLastTagByRepo($repo, $job)
  409. {
  410. if($repo->SCM == 'Subversion')
  411. {
  412. $dirs = $this->loadModel('svn')->getRepoTags($repo, $job->svnDir);
  413. if($dirs)
  414. {
  415. end($dirs);
  416. $lastTag = current($dirs);
  417. return rtrim($repo->path , '/') . '/' . trim($job->svnDir, '/') . '/' . $lastTag;
  418. }
  419. }
  420. else
  421. {
  422. $tags = $this->loadModel('git')->getRepoTags($repo);
  423. if($tags)
  424. {
  425. end($tags);
  426. return current($tags);
  427. }
  428. }
  429. return '';
  430. }
  431. /**
  432. * 根据版本库获取sonarqube框架的流水线。
  433. * Get sonarqube by RepoID.
  434. *
  435. * @param array $repoIDList
  436. * @param int $jobID
  437. * @param bool $showDeleted
  438. * @access public
  439. * @return array
  440. */
  441. public function getSonarqubeByRepo($repoIDList, $jobID = 0, $showDeleted = false)
  442. {
  443. return $this->dao->select('id,name,repo,deleted')->from(TABLE_JOB)
  444. ->where('frame')->eq('sonarqube')
  445. ->andWhere('repo')->in($repoIDList)
  446. ->beginIF(!$showDeleted)->andWhere('deleted')->eq('0')->fi()
  447. ->beginIF($jobID > 0)->andWhere('id')->ne($jobID)->fi()
  448. ->fetchAll('repo');
  449. }
  450. /**
  451. * 获取流水线键值对根据sonarqubeID或者sonarqube项目。
  452. * Get job pairs by sonarqube projectkeys.
  453. *
  454. * @param int $sonarqubeID
  455. * @param array $projectKeys
  456. * @param bool $emptyShowAll
  457. * @param bool $showDeleted
  458. * @access public
  459. * @return array|false
  460. */
  461. public function getJobBySonarqubeProject($sonarqubeID, $projectKeys = array(), $emptyShowAll = false, $showDeleted = false)
  462. {
  463. return $this->dao->select('projectKey,id')->from(TABLE_JOB)
  464. ->where('frame')->eq('sonarqube')
  465. ->andWhere('sonarqubeServer')->eq($sonarqubeID)
  466. ->beginIF(!$showDeleted)->andWhere('deleted')->eq('0')->fi()
  467. ->beginIF(!empty($projectKeys) or !$emptyShowAll)->andWhere('projectKey')->in($projectKeys)->fi()
  468. ->fetchPairs();
  469. }
  470. /**
  471. * 检查jenkins是否启用参数构建。
  472. * Check if jenkins has enabled parameterized build.
  473. *
  474. * @param string $url
  475. * @param string $userPWD
  476. * @access public
  477. * @return bool
  478. */
  479. public function checkParameterizedBuild($url, $userPWD)
  480. {
  481. $response = common::http($url, null, array(CURLOPT_HEADER => true, CURLOPT_USERPWD => $userPWD));
  482. return strpos($response, 'hudson.model.ParametersDefinitionProperty') !== false;
  483. }
  484. /**
  485. * 更新流水线最新tag。
  486. * Update job last tag.
  487. *
  488. * @param int $jobID
  489. * @param string $lastTag
  490. * @access protected
  491. * @return void
  492. */
  493. public function updateLastTag($jobID, $lastTag)
  494. {
  495. $this->dao->update(TABLE_JOB)->set('lastTag')->eq($lastTag)->where('id')->eq($jobID)->exec();
  496. }
  497. /**
  498. * 通过代码库ID导入该代码库的流水线。
  499. * Import the pipeline of the repository with the repoID.
  500. *
  501. * @param string|int $repoID
  502. * @return bool
  503. */
  504. public function import($repoID)
  505. {
  506. $repo = $this->loadModel('repo')->getByID((int)$repoID);
  507. if($repo->SCM != 'Gitlab') return false;
  508. $pipelines = $this->loadModel(strtolower($repo->SCM))->apiGetPipeline((int)$repo->serviceHost, (int)$repo->serviceProject, '');
  509. if(!is_array($pipelines) or empty($pipelines)) return false;
  510. $job = new stdclass();
  511. $job->name = $repo->name;
  512. $job->repo = $repoID;
  513. $job->product = is_numeric($repo->product) ? $repo->product : explode(',', $repo->product)[0];
  514. $job->engine = strtolower($repo->SCM);
  515. $job->server = $repo->serviceHost;
  516. $job->createdBy = 'system';
  517. $jobs = $this->dao->select('id, pipeline')->from(TABLE_JOB)->where('repo')->eq($repoID)->fetchPairs();
  518. $existsPipelines = array();
  519. foreach($jobs as $pipeline)
  520. {
  521. if(empty($pipeline)) continue;
  522. $pipeline = json_decode($pipeline);
  523. if(empty($pipeline)) continue;
  524. $existsPipelines[] = $pipeline->reference;
  525. }
  526. $addedPipelines = array();
  527. foreach($pipelines as $pipeline)
  528. {
  529. if(!empty($pipeline->disabled)) continue;
  530. $ref = isset($pipeline->ref) ? $pipeline->ref : $pipeline->default_branch;
  531. if(in_array($ref, $existsPipelines)) continue;
  532. $createdDate = helper::now();
  533. if(isset($pipeline->created_at)) $createdDate = date('Y-m-d H:i:s', strtotime($pipeline->created_at));
  534. $job->createdDate = $createdDate;
  535. if(isset($pipeline->updated_at)) $job->editedDate = date('Y-m-d H:i:s', strtotime($pipeline->updated_at));
  536. $pipelineMeta = array('project' => $repo->serviceProject, 'reference' => $ref);
  537. $job->pipeline = json_encode($pipelineMeta);
  538. $hash = md5($job->pipeline);
  539. if(array_key_exists($hash, array_flip($addedPipelines))) continue;
  540. $addedPipelines[] = $hash;
  541. $this->dao->insert(TABLE_JOB)->data($job)
  542. ->batchCheck($this->config->job->create->requiredFields, 'notempty')
  543. ->autoCheck()
  544. ->exec();
  545. if(dao::isError()) return false;
  546. $this->loadModel('action')->create('job', $this->dao->lastInsertId(), 'imported');
  547. }
  548. return true;
  549. }
  550. }